mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2026-03-02 22:01:52 +00:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5e92c6897 | ||
|
|
f1014a4810 | ||
|
|
1793424658 | ||
|
|
c94a5b948e | ||
|
|
107cabcd8b | ||
|
|
99bc769894 | ||
|
|
e8f1964941 | ||
|
|
1a491bec1c | ||
|
|
4ba63cdbf8 | ||
|
|
b2c2af3ebb | ||
|
|
9dca19fcb5 | ||
|
|
7c2dab43e1 | ||
|
|
a8c6d9b034 | ||
|
|
be0cb20fa2 | ||
|
|
2ac1025e9f | ||
|
|
27d79b574e | ||
|
|
1b82f42a33 | ||
|
|
f3f9eac67b | ||
|
|
2a86a0e540 | ||
|
|
d14aea708e | ||
|
|
12a05b422d | ||
|
|
a5abe9dbf2 | ||
|
|
07fcb4e016 | ||
|
|
e2cbb2713a | ||
|
|
706e00ace0 | ||
|
|
655fbf94c1 | ||
|
|
afc574aceb | ||
|
|
3da2b56426 | ||
|
|
5f91ad8819 | ||
|
|
131a745514 | ||
|
|
86349f1ad3 | ||
|
|
72e4a7b062 | ||
|
|
0a0165df45 | ||
|
|
992b22fd24 | ||
|
|
6fbe6c673b | ||
|
|
ff22e54b59 | ||
|
|
578365ab68 | ||
|
|
22905a2efc | ||
|
|
26bad75a1a | ||
|
|
04e1950591 | ||
|
|
340f4b8fb5 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -11,6 +11,6 @@ jobs:
|
|||||||
ci:
|
ci:
|
||||||
uses: shlinkio/github-actions/.github/workflows/web-app-ci.yml@main
|
uses: shlinkio/github-actions/.github/workflows/web-app-ci.yml@main
|
||||||
with:
|
with:
|
||||||
node-version: 18.12
|
node-version: 20.2
|
||||||
publish-coverage: true
|
publish-coverage: true
|
||||||
force-install: true
|
force-install: true
|
||||||
|
|||||||
6
.github/workflows/deploy-preview.yml
vendored
6
.github/workflows/deploy-preview.yml
vendored
@@ -9,14 +9,14 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
ref: ${{ github.event.pull_request.head.ref }}
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
- name: Use node.js
|
- name: Use node.js
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.12
|
node-version: 20.2
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
npm ci --force && \
|
npm ci --force && \
|
||||||
|
|||||||
3
.github/workflows/docker-image-build.yml
vendored
3
.github/workflows/docker-image-build.yml
vendored
@@ -2,8 +2,6 @@ name: Build and publish docker image
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
|
|
||||||
@@ -14,3 +12,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
image-name: shlinkio/shlink-web-client
|
image-name: shlinkio/shlink-web-client
|
||||||
version-arg-name: VERSION
|
version-arg-name: VERSION
|
||||||
|
platforms: 'linux/arm64/v8,linux/amd64'
|
||||||
|
|||||||
6
.github/workflows/publish-release.yml
vendored
6
.github/workflows/publish-release.yml
vendored
@@ -10,11 +10,11 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Use node.js
|
- name: Use node.js
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.12
|
node-version: 20.2
|
||||||
- name: Generate release assets
|
- name: Generate release assets
|
||||||
run: npm ci --force && VERSION=${GITHUB_REF#refs/tags/v} npm run build:dist
|
run: npm ci --force && VERSION=${GITHUB_REF#refs/tags/v} npm run build:dist
|
||||||
- name: Publish release with assets
|
- name: Publish release with assets
|
||||||
|
|||||||
35
CHANGELOG.md
35
CHANGELOG.md
@@ -4,6 +4,41 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).
|
||||||
|
|
||||||
|
## [3.10.2] - 2023-07-09
|
||||||
|
### Added
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* [#781](https://github.com/shlinkio/shlink-web-client/issues/781) Migrate tests from jest to vitest.
|
||||||
|
* [#843](https://github.com/shlinkio/shlink-web-client/issues/843) Build docker image only for new tags, making sure it always includes an actual version number.
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
|
||||||
|
## [3.10.1] - 2023-04-23
|
||||||
|
### Added
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* [#826](https://github.com/shlinkio/shlink-web-client/issues/826) Fix generated short URLs CSV so that it can be used to import on Shlink.
|
||||||
|
|
||||||
|
|
||||||
## [3.10.0] - 2023-03-19
|
## [3.10.0] - 2023-03-19
|
||||||
### Added
|
### Added
|
||||||
* [#807](https://github.com/shlinkio/shlink-web-client/issues/807) Add support for device-specific long-URLs when creating or editing short URLs.
|
* [#807](https://github.com/shlinkio/shlink-web-client/issues/807) Add support for device-specific long-URLs when creating or editing short URLs.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:18.12-alpine as node
|
FROM node:20.2-alpine as node
|
||||||
COPY . /shlink-web-client
|
COPY . /shlink-web-client
|
||||||
ARG VERSION="latest"
|
ARG VERSION="latest"
|
||||||
ENV VERSION ${VERSION}
|
ENV VERSION ${VERSION}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
['@babel/preset-env', {
|
|
||||||
targets: { esmodules: true }
|
|
||||||
}],
|
|
||||||
['@babel/preset-react', { runtime: 'automatic' }],
|
|
||||||
'@babel/preset-typescript',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
// This is a custom Jest transformer turning style imports into empty objects.
|
|
||||||
// http://facebook.github.io/jest/docs/en/webpack.html
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
process() {
|
|
||||||
return { code: 'module.exports = {};' };
|
|
||||||
},
|
|
||||||
getCacheKey() {
|
|
||||||
// The output is always the same.
|
|
||||||
return 'cssTransform';
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
// This is a custom Jest transformer turning file imports into filenames.
|
|
||||||
// http://facebook.github.io/jest/docs/en/webpack.html
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
process(src, filename) {
|
|
||||||
const assetFilename = JSON.stringify(path.basename(filename));
|
|
||||||
|
|
||||||
if (filename.match(/\.svg$/)) {
|
|
||||||
return `module.exports = {
|
|
||||||
__esModule: true,
|
|
||||||
default: ${assetFilename},
|
|
||||||
ReactComponent: (props) => ({
|
|
||||||
$$typeof: Symbol.for('react.element'),
|
|
||||||
type: 'svg',
|
|
||||||
ref: null,
|
|
||||||
key: null,
|
|
||||||
props: Object.assign({}, props, {
|
|
||||||
children: ${assetFilename}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
};`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
code: `module.exports = ${assetFilename};`
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import '@testing-library/jest-dom';
|
|
||||||
import 'jest-canvas-mock';
|
|
||||||
import 'chart.js/auto';
|
|
||||||
import ResizeObserver from 'resize-observer-polyfill';
|
|
||||||
import { setAutoFreeze } from 'immer';
|
|
||||||
|
|
||||||
(global as any).ResizeObserver = ResizeObserver;
|
|
||||||
(global as any).scrollTo = () => {};
|
|
||||||
(global as any).prompt = () => {};
|
|
||||||
(global as any).matchMedia = (media: string) => ({ matches: false, media });
|
|
||||||
|
|
||||||
setAutoFreeze(false); // TODO Bypassing a bug on jest
|
|
||||||
27
config/test/setupTests.ts
Normal file
27
config/test/setupTests.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import 'vitest-canvas-mock';
|
||||||
|
import 'chart.js/auto';
|
||||||
|
import type { TestingLibraryMatchers } from '@testing-library/jest-dom/matchers';
|
||||||
|
import matchers from '@testing-library/jest-dom/matchers';
|
||||||
|
import { cleanup } from '@testing-library/react';
|
||||||
|
import ResizeObserver from 'resize-observer-polyfill';
|
||||||
|
import { afterEach, expect } from 'vitest';
|
||||||
|
|
||||||
|
// Workaround for TypeScript error: https://github.com/testing-library/jest-dom/issues/439#issuecomment-1536524120
|
||||||
|
declare module 'vitest' {
|
||||||
|
interface Assertion<T = any> extends jest.Matchers<void, T>, TestingLibraryMatchers<T, void> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extends Vitest's expect method with methods from react-testing-library
|
||||||
|
expect.extend(matchers);
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Clears all mocks after every test
|
||||||
|
vi.clearAllMocks();
|
||||||
|
// Run a cleanup after each test case (e.g. clearing jsdom)
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
(global as any).ResizeObserver = ResizeObserver;
|
||||||
|
(global as any).scrollTo = () => {};
|
||||||
|
(global as any).prompt = () => {};
|
||||||
|
(global as any).matchMedia = (media: string) => ({ matches: false, media });
|
||||||
@@ -3,7 +3,7 @@ version: '3'
|
|||||||
services:
|
services:
|
||||||
shlink_web_client_node:
|
shlink_web_client_node:
|
||||||
container_name: shlink_web_client_node
|
container_name: shlink_web_client_node
|
||||||
image: node:18.12-alpine
|
image: node:20.2-alpine
|
||||||
command: /bin/sh -c "cd /home/shlink/www && npm install --force && npm run start"
|
command: /bin/sh -c "cd /home/shlink/www && npm install --force && npm run start"
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/home/shlink/www
|
- ./:/home/shlink/www
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
coverageDirectory: '<rootDir>/coverage',
|
|
||||||
collectCoverageFrom: [
|
|
||||||
'src/**/*.{ts,tsx}',
|
|
||||||
'!src/*.{ts,tsx}',
|
|
||||||
'!src/reducers/index.ts',
|
|
||||||
'!src/**/provideServices.ts',
|
|
||||||
'!src/container/*.ts',
|
|
||||||
],
|
|
||||||
coverageThreshold: {
|
|
||||||
global: {
|
|
||||||
statements: 90,
|
|
||||||
branches: 85,
|
|
||||||
functions: 90,
|
|
||||||
lines: 90,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setupFilesAfterEnv: ['<rootDir>/config/jest/setupTests.ts'],
|
|
||||||
testMatch: ['<rootDir>/test/**/*.test.{ts,tsx}'],
|
|
||||||
testEnvironment: 'jsdom',
|
|
||||||
testEnvironmentOptions: {
|
|
||||||
url: 'http://localhost',
|
|
||||||
},
|
|
||||||
transform: {
|
|
||||||
'^.+\\.(ts|tsx|js)$': '<rootDir>/node_modules/babel-jest',
|
|
||||||
'^.+\\.scss$': '<rootDir>/config/jest/cssTransform.js',
|
|
||||||
'^(?!.*\\.(ts|tsx|js|json|scss)$)': '<rootDir>/config/jest/fileTransform.js',
|
|
||||||
},
|
|
||||||
transformIgnorePatterns: [
|
|
||||||
'node_modules\/(?!(\@react-leaflet|react-leaflet|leaflet|react-chartjs-2|react-colorful)\/)',
|
|
||||||
'^.+\\.module\\.scss$',
|
|
||||||
],
|
|
||||||
moduleNameMapper: {
|
|
||||||
'^.+\\.module\\.scss$': 'identity-obj-proxy',
|
|
||||||
'react-chartjs-2': '<rootDir>/node_modules/react-chartjs-2/dist/index.js',
|
|
||||||
'uuid': '<rootDir>/node_modules/uuid/dist/index.js',
|
|
||||||
},
|
|
||||||
moduleFileExtensions: ['js', 'ts', 'tsx', 'json'],
|
|
||||||
};
|
|
||||||
8903
package-lock.json
generated
8903
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
@@ -17,16 +17,12 @@
|
|||||||
"preview": "vite preview --host=0.0.0.0",
|
"preview": "vite preview --host=0.0.0.0",
|
||||||
"build": "npm run types && vite build && node scripts/replace-version.mjs",
|
"build": "npm run types && vite build && node scripts/replace-version.mjs",
|
||||||
"build:dist": "npm run build && node scripts/create-dist-file.mjs",
|
"build:dist": "npm run build && node scripts/create-dist-file.mjs",
|
||||||
"test": "jest --env=jsdom --colors",
|
"test": "vitest run --run",
|
||||||
"test:coverage": "npm run test -- --coverage --coverageReporters=text --coverageReporters=text-summary",
|
"test:watch": "vitest --watch",
|
||||||
"test:ci": "npm run test:coverage -- --coverageReporters=clover --ci",
|
"test:ci": "npm run test -- --coverage",
|
||||||
"test:pretty": "npm run test:coverage -- --coverageReporters=html",
|
|
||||||
"test:verbose": "npm run test -- --verbose"
|
"test:verbose": "npm run test -- --verbose"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/preset-env": "^7.20.2",
|
|
||||||
"@babel/preset-react": "^7.18.6",
|
|
||||||
"@babel/preset-typescript": "^7.21.0",
|
|
||||||
"@fortawesome/fontawesome-free": "^6.3.0",
|
"@fortawesome/fontawesome-free": "^6.3.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.3.0",
|
"@fortawesome/fontawesome-svg-core": "^6.3.0",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.3.0",
|
"@fortawesome/free-brands-svg-icons": "^6.3.0",
|
||||||
@@ -54,16 +50,14 @@
|
|||||||
"react-copy-to-clipboard": "^5.1.0",
|
"react-copy-to-clipboard": "^5.1.0",
|
||||||
"react-datepicker": "^4.8.0",
|
"react-datepicker": "^4.8.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-external-link": "^2.0.0",
|
"react-external-link": "^2.2.0",
|
||||||
"react-leaflet": "^4.2.0",
|
"react-leaflet": "^4.2.0",
|
||||||
"react-redux": "^8.0.5",
|
"react-redux": "^8.0.5",
|
||||||
"react-router-dom": "^6.6.1",
|
"react-router-dom": "^6.6.1",
|
||||||
"react-swipeable": "^7.0.0",
|
"react-swipeable": "^7.0.0",
|
||||||
"react-tag-autocomplete": "^6.3.0",
|
"react-tag-autocomplete": "^6.3.0",
|
||||||
"reactstrap": "^9.1.5",
|
"reactstrap": "^9.1.5",
|
||||||
"redux": "^4.2.0",
|
|
||||||
"redux-localstorage-simple": "^2.5.1",
|
"redux-localstorage-simple": "^2.5.1",
|
||||||
"redux-thunk": "^2.4.2",
|
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"workbox-core": "^6.5.4",
|
"workbox-core": "^6.5.4",
|
||||||
"workbox-expiration": "^6.5.4",
|
"workbox-expiration": "^6.5.4",
|
||||||
@@ -75,9 +69,9 @@
|
|||||||
"@shlinkio/eslint-config-js-coding-standard": "~2.1.0",
|
"@shlinkio/eslint-config-js-coding-standard": "~2.1.0",
|
||||||
"@shlinkio/stylelint-config-css-coding-standard": "~1.0.1",
|
"@shlinkio/stylelint-config-css-coding-standard": "~1.0.1",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.4.3",
|
||||||
"@types/jest": "^29.2.4",
|
"@total-typescript/shoehorn": "^0.1.0",
|
||||||
"@types/json2csv": "^5.0.3",
|
"@types/json2csv": "^5.0.3",
|
||||||
"@types/leaflet": "^1.9.0",
|
"@types/leaflet": "^1.9.0",
|
||||||
"@types/qs": "^6.9.7",
|
"@types/qs": "^6.9.7",
|
||||||
@@ -89,22 +83,20 @@
|
|||||||
"@types/react-dom": "^18.0.10",
|
"@types/react-dom": "^18.0.10",
|
||||||
"@types/react-tag-autocomplete": "^6.3.0",
|
"@types/react-tag-autocomplete": "^6.3.0",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"@vitejs/plugin-react": "^3.1.0",
|
"@vitejs/plugin-react": "^4.0.0",
|
||||||
|
"@vitest/coverage-v8": "^0.32.0",
|
||||||
"adm-zip": "^0.5.10",
|
"adm-zip": "^0.5.10",
|
||||||
"babel-jest": "^29.5.0",
|
|
||||||
"chalk": "^5.2.0",
|
"chalk": "^5.2.0",
|
||||||
"eslint": "^8.30.0",
|
"eslint": "^8.30.0",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"jsdom": "^22.0.0",
|
||||||
"jest": "^29.3.1",
|
|
||||||
"jest-canvas-mock": "^2.4.0",
|
|
||||||
"jest-environment-jsdom": "^29.3.1",
|
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"sass": "^1.57.1",
|
"sass": "^1.57.1",
|
||||||
"stylelint": "^14.16.0",
|
"stylelint": "^15.10.1",
|
||||||
"ts-mockery": "^1.2.0",
|
|
||||||
"typescript": "^5.0.2",
|
"typescript": "^5.0.2",
|
||||||
"vite": "^4.2.0",
|
"vite": "^4.3.9",
|
||||||
"vite-plugin-pwa": "^0.14.4"
|
"vite-plugin-pwa": "^0.14.4",
|
||||||
|
"vitest": "^0.32.0",
|
||||||
|
"vitest-canvas-mock": "^0.2.2"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
">0.2%",
|
">0.2%",
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ export class ReportExporter {
|
|||||||
|
|
||||||
private readonly exportCsv = (filename: string, rows: object[]) => {
|
private readonly exportCsv = (filename: string, rows: object[]) => {
|
||||||
const csv = this.jsonToCsv(rows);
|
const csv = this.jsonToCsv(rows);
|
||||||
|
|
||||||
saveCsv(this.window, csv, filename);
|
saveCsv(this.window, csv, filename);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import { Link } from 'react-router-dom';
|
|||||||
import { DropdownItem } from 'reactstrap';
|
import { DropdownItem } from 'reactstrap';
|
||||||
import type { SelectedServer } from '../../servers/data';
|
import type { SelectedServer } from '../../servers/data';
|
||||||
import { getServerId } from '../../servers/data';
|
import { getServerId } from '../../servers/data';
|
||||||
import { DropdownBtnMenu } from '../../utils/DropdownBtnMenu';
|
|
||||||
import { useFeature } from '../../utils/helpers/features';
|
import { useFeature } from '../../utils/helpers/features';
|
||||||
import { useToggle } from '../../utils/helpers/hooks';
|
import { useToggle } from '../../utils/helpers/hooks';
|
||||||
|
import { RowDropdownBtn } from '../../utils/RowDropdownBtn';
|
||||||
import { DEFAULT_DOMAIN } from '../../visits/reducers/domainVisits';
|
import { DEFAULT_DOMAIN } from '../../visits/reducers/domainVisits';
|
||||||
import type { Domain } from '../data';
|
import type { Domain } from '../data';
|
||||||
import type { EditDomainRedirects } from '../reducers/domainRedirects';
|
import type { EditDomainRedirects } from '../reducers/domainRedirects';
|
||||||
@@ -20,7 +20,6 @@ interface DomainDropdownProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const DomainDropdown: FC<DomainDropdownProps> = ({ domain, editDomainRedirects, selectedServer }) => {
|
export const DomainDropdown: FC<DomainDropdownProps> = ({ domain, editDomainRedirects, selectedServer }) => {
|
||||||
const [isOpen, toggle] = useToggle();
|
|
||||||
const [isModalOpen, toggleModal] = useToggle();
|
const [isModalOpen, toggleModal] = useToggle();
|
||||||
const { isDefault } = domain;
|
const { isDefault } = domain;
|
||||||
const canBeEdited = !isDefault || useFeature('defaultDomainRedirectsEdition', selectedServer);
|
const canBeEdited = !isDefault || useFeature('defaultDomainRedirectsEdition', selectedServer);
|
||||||
@@ -28,7 +27,7 @@ export const DomainDropdown: FC<DomainDropdownProps> = ({ domain, editDomainRedi
|
|||||||
const serverId = getServerId(selectedServer);
|
const serverId = getServerId(selectedServer);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownBtnMenu isOpen={isOpen} toggle={toggle}>
|
<RowDropdownBtn>
|
||||||
{withVisits && (
|
{withVisits && (
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
tag={Link}
|
tag={Link}
|
||||||
@@ -47,6 +46,6 @@ export const DomainDropdown: FC<DomainDropdownProps> = ({ domain, editDomainRedi
|
|||||||
toggle={toggleModal}
|
toggle={toggleModal}
|
||||||
editDomainRedirects={editDomainRedirects}
|
editDomainRedirects={editDomainRedirects}
|
||||||
/>
|
/>
|
||||||
</DropdownBtnMenu>
|
</RowDropdownBtn>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { DropdownItem } from 'reactstrap';
|
import { DropdownItem } from 'reactstrap';
|
||||||
import { DropdownBtnMenu } from '../utils/DropdownBtnMenu';
|
|
||||||
import { useToggle } from '../utils/helpers/hooks';
|
import { useToggle } from '../utils/helpers/hooks';
|
||||||
|
import { RowDropdownBtn } from '../utils/RowDropdownBtn';
|
||||||
import type { ServerWithId } from './data';
|
import type { ServerWithId } from './data';
|
||||||
import type { DeleteServerModalProps } from './DeleteServerModal';
|
import type { DeleteServerModalProps } from './DeleteServerModal';
|
||||||
|
|
||||||
@@ -25,14 +25,13 @@ interface ManageServersRowDropdownConnectProps extends ManageServersRowDropdownP
|
|||||||
export const ManageServersRowDropdown = (
|
export const ManageServersRowDropdown = (
|
||||||
DeleteServerModal: FC<DeleteServerModalProps>,
|
DeleteServerModal: FC<DeleteServerModalProps>,
|
||||||
): FC<ManageServersRowDropdownConnectProps> => ({ server, setAutoConnect }) => {
|
): FC<ManageServersRowDropdownConnectProps> => ({ server, setAutoConnect }) => {
|
||||||
const [isMenuOpen, toggleMenu] = useToggle();
|
|
||||||
const [isModalOpen,, showModal, hideModal] = useToggle();
|
const [isModalOpen,, showModal, hideModal] = useToggle();
|
||||||
const serverUrl = `/server/${server.id}`;
|
const serverUrl = `/server/${server.id}`;
|
||||||
const { autoConnect: isAutoConnect } = server;
|
const { autoConnect: isAutoConnect } = server;
|
||||||
const autoConnectIcon = isAutoConnect ? toggleOffIcon : toggleOnIcon;
|
const autoConnectIcon = isAutoConnect ? toggleOffIcon : toggleOnIcon;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownBtnMenu isOpen={isMenuOpen} toggle={toggleMenu}>
|
<RowDropdownBtn minWidth={170}>
|
||||||
<DropdownItem tag={Link} to={serverUrl}>
|
<DropdownItem tag={Link} to={serverUrl}>
|
||||||
<FontAwesomeIcon icon={connectIcon} fixedWidth /> Connect
|
<FontAwesomeIcon icon={connectIcon} fixedWidth /> Connect
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
@@ -48,6 +47,6 @@ export const ManageServersRowDropdown = (
|
|||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
|
|
||||||
<DeleteServerModal redirectHome={false} server={server} isOpen={isModalOpen} toggle={hideModal} />
|
<DeleteServerModal redirectHome={false} server={server} isOpen={isModalOpen} toggle={hideModal} />
|
||||||
</DropdownBtnMenu>
|
</RowDropdownBtn>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ export interface ExportableShortUrl {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
title: string;
|
title: string;
|
||||||
shortUrl: string;
|
shortUrl: string;
|
||||||
|
domain?: string;
|
||||||
|
shortCode: string;
|
||||||
longUrl: string;
|
longUrl: string;
|
||||||
tags: string;
|
tags: string;
|
||||||
visits: number;
|
visits: number;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
import { useCallback } from 'react';
|
||||||
import type { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
|
import type { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
|
||||||
import type { ReportExporter } from '../../common/services/ReportExporter';
|
import type { ReportExporter } from '../../common/services/ReportExporter';
|
||||||
import type { SelectedServer } from '../../servers/data';
|
import type { SelectedServer } from '../../servers/data';
|
||||||
@@ -24,7 +25,7 @@ export const ExportShortUrlsBtn = (
|
|||||||
): FC<ExportShortUrlsBtnConnectProps> => ({ amount = 0, selectedServer }) => {
|
): FC<ExportShortUrlsBtnConnectProps> => ({ amount = 0, selectedServer }) => {
|
||||||
const [{ tags, search, startDate, endDate, orderBy, tagsMode }] = useShortUrlsQuery();
|
const [{ tags, search, startDate, endDate, orderBy, tagsMode }] = useShortUrlsQuery();
|
||||||
const [loading,, startLoading, stopLoading] = useToggle();
|
const [loading,, startLoading, stopLoading] = useToggle();
|
||||||
const exportAllUrls = async () => {
|
const exportAllUrls = useCallback(async () => {
|
||||||
if (!isServerWithId(selectedServer)) {
|
if (!isServerWithId(selectedServer)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -47,16 +48,23 @@ export const ExportShortUrlsBtn = (
|
|||||||
startLoading();
|
startLoading();
|
||||||
const shortUrls = await loadAllUrls();
|
const shortUrls = await loadAllUrls();
|
||||||
|
|
||||||
exportShortUrls(shortUrls.map((shortUrl) => ({
|
exportShortUrls(shortUrls.map((shortUrl) => {
|
||||||
createdAt: shortUrl.dateCreated,
|
const { hostname: domain, pathname } = new URL(shortUrl.shortUrl);
|
||||||
shortUrl: shortUrl.shortUrl,
|
const shortCode = pathname.substring(1); // Remove trailing slash
|
||||||
longUrl: shortUrl.longUrl,
|
|
||||||
title: shortUrl.title ?? '',
|
return {
|
||||||
tags: shortUrl.tags.join(','),
|
createdAt: shortUrl.dateCreated,
|
||||||
visits: shortUrl?.visitsSummary?.total ?? shortUrl.visitsCount,
|
domain,
|
||||||
})));
|
shortCode,
|
||||||
|
shortUrl: shortUrl.shortUrl,
|
||||||
|
longUrl: shortUrl.longUrl,
|
||||||
|
title: shortUrl.title ?? '',
|
||||||
|
tags: shortUrl.tags.join('|'),
|
||||||
|
visits: shortUrl?.visitsSummary?.total ?? shortUrl.visitsCount,
|
||||||
|
};
|
||||||
|
}));
|
||||||
stopLoading();
|
stopLoading();
|
||||||
};
|
}, [selectedServer]);
|
||||||
|
|
||||||
return <ExportBtn loading={loading} className="btn-md-block" amount={amount} onClick={exportAllUrls} />;
|
return <ExportBtn loading={loading} className="btn-md-block" amount={amount} onClick={exportAllUrls} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const ShortUrlsFilterDropdown = (
|
|||||||
const onFilterClick = (key: keyof ShortUrlsFilter) => () => onChange({ ...selected, [key]: !selected?.[key] });
|
const onFilterClick = (key: keyof ShortUrlsFilter) => () => onChange({ ...selected, [key]: !selected?.[key] });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownBtn text="Filters" dropdownClassName={className} className="me-3" right minWidth={250}>
|
<DropdownBtn text="Filters" dropdownClassName={className} inline end minWidth={250}>
|
||||||
<DropdownItem header>Visits:</DropdownItem>
|
<DropdownItem header>Visits:</DropdownItem>
|
||||||
<DropdownItem active={excludeBots} onClick={onFilterClick('excludeBots')}>Ignore visits from bots</DropdownItem>
|
<DropdownItem active={excludeBots} onClick={onFilterClick('excludeBots')}>Ignore visits from bots</DropdownItem>
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export const ShortUrlsRow = (
|
|||||||
<td className="responsive-table__cell short-urls-row__cell" data-th="Status">
|
<td className="responsive-table__cell short-urls-row__cell" data-th="Status">
|
||||||
<ShortUrlStatus shortUrl={shortUrl} />
|
<ShortUrlStatus shortUrl={shortUrl} />
|
||||||
</td>
|
</td>
|
||||||
<td className="responsive-table__cell short-urls-row__cell">
|
<td className="responsive-table__cell short-urls-row__cell text-end">
|
||||||
<ShortUrlsRowMenu selectedServer={selectedServer} shortUrl={shortUrl} />
|
<ShortUrlsRowMenu selectedServer={selectedServer} shortUrl={shortUrl} />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { DropdownItem } from 'reactstrap';
|
import { DropdownItem } from 'reactstrap';
|
||||||
import type { SelectedServer } from '../../servers/data';
|
import type { SelectedServer } from '../../servers/data';
|
||||||
import { DropdownBtnMenu } from '../../utils/DropdownBtnMenu';
|
|
||||||
import { useToggle } from '../../utils/helpers/hooks';
|
import { useToggle } from '../../utils/helpers/hooks';
|
||||||
|
import { RowDropdownBtn } from '../../utils/RowDropdownBtn';
|
||||||
import type { ShortUrl, ShortUrlModalProps } from '../data';
|
import type { ShortUrl, ShortUrlModalProps } from '../data';
|
||||||
import { ShortUrlDetailLink } from './ShortUrlDetailLink';
|
import { ShortUrlDetailLink } from './ShortUrlDetailLink';
|
||||||
|
|
||||||
@@ -23,12 +23,11 @@ export const ShortUrlsRowMenu = (
|
|||||||
DeleteShortUrlModal: ShortUrlModal,
|
DeleteShortUrlModal: ShortUrlModal,
|
||||||
QrCodeModal: ShortUrlModal,
|
QrCodeModal: ShortUrlModal,
|
||||||
) => ({ shortUrl, selectedServer }: ShortUrlsRowMenuProps) => {
|
) => ({ shortUrl, selectedServer }: ShortUrlsRowMenuProps) => {
|
||||||
const [isOpen, toggle] = useToggle();
|
|
||||||
const [isQrModalOpen,, openQrCodeModal, closeQrCodeModal] = useToggle();
|
const [isQrModalOpen,, openQrCodeModal, closeQrCodeModal] = useToggle();
|
||||||
const [isDeleteModalOpen,, openDeleteModal, closeDeleteModal] = useToggle();
|
const [isDeleteModalOpen,, openDeleteModal, closeDeleteModal] = useToggle();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownBtnMenu toggle={toggle} isOpen={isOpen}>
|
<RowDropdownBtn minWidth={190}>
|
||||||
<DropdownItem tag={ShortUrlDetailLink} selectedServer={selectedServer} shortUrl={shortUrl} suffix="visits">
|
<DropdownItem tag={ShortUrlDetailLink} selectedServer={selectedServer} shortUrl={shortUrl} suffix="visits">
|
||||||
<FontAwesomeIcon icon={pieChartIcon} fixedWidth /> Visit stats
|
<FontAwesomeIcon icon={pieChartIcon} fixedWidth /> Visit stats
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
@@ -48,7 +47,7 @@ export const ShortUrlsRowMenu = (
|
|||||||
<FontAwesomeIcon icon={deleteIcon} fixedWidth /> Delete short URL
|
<FontAwesomeIcon icon={deleteIcon} fixedWidth /> Delete short URL
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DeleteShortUrlModal shortUrl={shortUrl} isOpen={isDeleteModalOpen} toggle={closeDeleteModal} />
|
<DeleteShortUrlModal shortUrl={shortUrl} isOpen={isDeleteModalOpen} toggle={closeDeleteModal} />
|
||||||
</DropdownBtnMenu>
|
</RowDropdownBtn>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import { Link } from 'react-router-dom';
|
|||||||
import { DropdownItem } from 'reactstrap';
|
import { DropdownItem } from 'reactstrap';
|
||||||
import type { SelectedServer } from '../servers/data';
|
import type { SelectedServer } from '../servers/data';
|
||||||
import { getServerId } from '../servers/data';
|
import { getServerId } from '../servers/data';
|
||||||
import { DropdownBtnMenu } from '../utils/DropdownBtnMenu';
|
|
||||||
import { useToggle } from '../utils/helpers/hooks';
|
import { useToggle } from '../utils/helpers/hooks';
|
||||||
import { prettify } from '../utils/helpers/numbers';
|
import { prettify } from '../utils/helpers/numbers';
|
||||||
|
import { RowDropdownBtn } from '../utils/RowDropdownBtn';
|
||||||
import type { ColorGenerator } from '../utils/services/ColorGenerator';
|
import type { ColorGenerator } from '../utils/services/ColorGenerator';
|
||||||
import type { SimplifiedTag, TagModalProps } from './data';
|
import type { SimplifiedTag, TagModalProps } from './data';
|
||||||
import { TagBullet } from './helpers/TagBullet';
|
import { TagBullet } from './helpers/TagBullet';
|
||||||
@@ -24,7 +24,6 @@ export const TagsTableRow = (
|
|||||||
) => ({ tag, selectedServer }: TagsTableRowProps) => {
|
) => ({ tag, selectedServer }: TagsTableRowProps) => {
|
||||||
const [isDeleteModalOpen, toggleDelete] = useToggle();
|
const [isDeleteModalOpen, toggleDelete] = useToggle();
|
||||||
const [isEditModalOpen, toggleEdit] = useToggle();
|
const [isEditModalOpen, toggleEdit] = useToggle();
|
||||||
const [isDropdownOpen, toggleDropdown] = useToggle();
|
|
||||||
const serverId = getServerId(selectedServer);
|
const serverId = getServerId(selectedServer);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -43,14 +42,14 @@ export const TagsTableRow = (
|
|||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
<td className="responsive-table__cell text-lg-end">
|
<td className="responsive-table__cell text-lg-end">
|
||||||
<DropdownBtnMenu toggle={toggleDropdown} isOpen={isDropdownOpen}>
|
<RowDropdownBtn>
|
||||||
<DropdownItem onClick={toggleEdit}>
|
<DropdownItem onClick={toggleEdit}>
|
||||||
<FontAwesomeIcon icon={editIcon} fixedWidth className="me-1" /> Edit
|
<FontAwesomeIcon icon={editIcon} fixedWidth className="me-1" /> Edit
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem onClick={toggleDelete}>
|
<DropdownItem onClick={toggleDelete}>
|
||||||
<FontAwesomeIcon icon={deleteIcon} fixedWidth className="me-1" /> Delete
|
<FontAwesomeIcon icon={deleteIcon} fixedWidth className="me-1" /> Delete
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</DropdownBtnMenu>
|
</RowDropdownBtn>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<EditTagModal tag={tag.tag} toggle={toggleEdit} isOpen={isEditModalOpen} />
|
<EditTagModal tag={tag.tag} toggle={toggleEdit} isOpen={isEditModalOpen} />
|
||||||
|
|||||||
@@ -2,13 +2,20 @@
|
|||||||
|
|
||||||
@import '../utils/mixins/vertical-align';
|
@import '../utils/mixins/vertical-align';
|
||||||
|
|
||||||
|
.dropdown-btn__toggle.dropdown-btn__toggle {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-btn__toggle.dropdown-btn__toggle--with-caret {
|
||||||
|
padding-right: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.dropdown-btn__toggle.dropdown-btn__toggle,
|
.dropdown-btn__toggle.dropdown-btn__toggle,
|
||||||
.dropdown-btn__toggle.dropdown-btn__toggle:not(:disabled):not(.disabled).active,
|
.dropdown-btn__toggle.dropdown-btn__toggle:not(:disabled):not(.disabled).active,
|
||||||
.dropdown-btn__toggle.dropdown-btn__toggle:not(:disabled):not(.disabled):active,
|
.dropdown-btn__toggle.dropdown-btn__toggle:not(:disabled):not(.disabled):active,
|
||||||
.dropdown-btn__toggle.dropdown-btn__toggle:not(:disabled):not(.disabled):focus,
|
.dropdown-btn__toggle.dropdown-btn__toggle:not(:disabled):not(.disabled):focus,
|
||||||
.dropdown-btn__toggle.dropdown-btn__toggle:not(:disabled):not(.disabled):hover,
|
.dropdown-btn__toggle.dropdown-btn__toggle:not(:disabled):not(.disabled):hover,
|
||||||
.show > .dropdown-btn__toggle.dropdown-btn__toggle.dropdown-toggle {
|
.show > .dropdown-btn__toggle.dropdown-btn__toggle.dropdown-toggle {
|
||||||
text-align: left;
|
|
||||||
color: var(--input-text-color);
|
color: var(--input-text-color);
|
||||||
background-color: var(--primary-color);
|
background-color: var(--primary-color);
|
||||||
border-color: var(--input-border-color);
|
border-color: var(--input-border-color);
|
||||||
|
|||||||
@@ -1,28 +1,45 @@
|
|||||||
import type { FC, PropsWithChildren } from 'react';
|
import classNames from 'classnames';
|
||||||
|
import type { FC, PropsWithChildren, ReactNode } from 'react';
|
||||||
import { Dropdown, DropdownMenu, DropdownToggle } from 'reactstrap';
|
import { Dropdown, DropdownMenu, DropdownToggle } from 'reactstrap';
|
||||||
|
import type { DropdownToggleProps } from 'reactstrap/types/lib/DropdownToggle';
|
||||||
import { useToggle } from './helpers/hooks';
|
import { useToggle } from './helpers/hooks';
|
||||||
import './DropdownBtn.scss';
|
import './DropdownBtn.scss';
|
||||||
|
|
||||||
export type DropdownBtnProps = PropsWithChildren<{
|
export type DropdownBtnProps = PropsWithChildren<Omit<DropdownToggleProps, 'caret' | 'size' | 'outline'> & {
|
||||||
text: string;
|
text: ReactNode;
|
||||||
disabled?: boolean;
|
noCaret?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
dropdownClassName?: string;
|
dropdownClassName?: string;
|
||||||
right?: boolean;
|
inline?: boolean;
|
||||||
minWidth?: number;
|
minWidth?: number;
|
||||||
|
size?: 'sm' | 'md' | 'lg';
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const DropdownBtn: FC<DropdownBtnProps> = (
|
export const DropdownBtn: FC<DropdownBtnProps> = ({
|
||||||
{ text, disabled = false, className = '', children, dropdownClassName, right = false, minWidth },
|
text,
|
||||||
) => {
|
disabled = false,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
dropdownClassName,
|
||||||
|
noCaret,
|
||||||
|
end = false,
|
||||||
|
minWidth,
|
||||||
|
inline,
|
||||||
|
size,
|
||||||
|
}) => {
|
||||||
const [isOpen, toggle] = useToggle();
|
const [isOpen, toggle] = useToggle();
|
||||||
const toggleClasses = `dropdown-btn__toggle btn-block ${className}`;
|
const toggleClasses = classNames('dropdown-btn__toggle', className, {
|
||||||
const style = { minWidth: minWidth && `${minWidth}px` };
|
'btn-block': !inline,
|
||||||
|
'dropdown-btn__toggle--with-caret': !noCaret,
|
||||||
|
});
|
||||||
|
const menuStyle = { minWidth: minWidth && `${minWidth}px` };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown isOpen={isOpen} toggle={toggle} disabled={disabled} className={dropdownClassName}>
|
<Dropdown isOpen={isOpen} toggle={toggle} disabled={disabled} className={dropdownClassName}>
|
||||||
<DropdownToggle caret className={toggleClasses} color="primary">{text}</DropdownToggle>
|
<DropdownToggle size={size} caret={!noCaret} className={toggleClasses} color="primary">
|
||||||
<DropdownMenu className="w-100" end={right} style={style}>{children}</DropdownMenu>
|
{text}
|
||||||
|
</DropdownToggle>
|
||||||
|
<DropdownMenu className="w-100" end={end} style={menuStyle}>{children}</DropdownMenu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
.dropdown-btn-menu__dropdown-toggle:after {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { faEllipsisV as menuIcon } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import type { FC, PropsWithChildren } from 'react';
|
|
||||||
import { ButtonDropdown, DropdownMenu, DropdownToggle } from 'reactstrap';
|
|
||||||
import './DropdownBtnMenu.scss';
|
|
||||||
|
|
||||||
export type DropdownBtnMenuProps = PropsWithChildren<{
|
|
||||||
isOpen: boolean;
|
|
||||||
toggle: () => void;
|
|
||||||
right?: boolean;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export const DropdownBtnMenu: FC<DropdownBtnMenuProps> = ({ isOpen, toggle, children, right = true }) => (
|
|
||||||
<ButtonDropdown toggle={toggle} isOpen={isOpen}>
|
|
||||||
<DropdownToggle size="sm" caret outline className="dropdown-btn-menu__dropdown-toggle">
|
|
||||||
<FontAwesomeIcon icon={menuIcon} />
|
|
||||||
</DropdownToggle>
|
|
||||||
<DropdownMenu end={right}>{children}</DropdownMenu>
|
|
||||||
</ButtonDropdown>
|
|
||||||
);
|
|
||||||
@@ -5,10 +5,10 @@ import type { ButtonProps } from 'reactstrap';
|
|||||||
import { Button } from 'reactstrap';
|
import { Button } from 'reactstrap';
|
||||||
import { prettify } from './helpers/numbers';
|
import { prettify } from './helpers/numbers';
|
||||||
|
|
||||||
interface ExportBtnProps extends Omit<ButtonProps, 'outline' | 'color' | 'disabled'> {
|
type ExportBtnProps = Omit<ButtonProps, 'outline' | 'color' | 'disabled'> & {
|
||||||
amount?: number;
|
amount?: number;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const ExportBtn: FC<ExportBtnProps> = ({ amount = 0, loading = false, ...rest }) => (
|
export const ExportBtn: FC<ExportBtnProps> = ({ amount = 0, loading = false, ...rest }) => (
|
||||||
<Button {...rest} outline color="primary" disabled={loading}>
|
<Button {...rest} outline color="primary" disabled={loading}>
|
||||||
|
|||||||
21
src/utils/RowDropdownBtn.tsx
Normal file
21
src/utils/RowDropdownBtn.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { faEllipsisV as menuIcon } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import type { FC, PropsWithChildren } from 'react';
|
||||||
|
import { DropdownBtn } from './DropdownBtn';
|
||||||
|
|
||||||
|
export type DropdownBtnMenuProps = PropsWithChildren<{
|
||||||
|
minWidth?: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export const RowDropdownBtn: FC<DropdownBtnMenuProps> = ({ children, minWidth }) => (
|
||||||
|
<DropdownBtn
|
||||||
|
text={<FontAwesomeIcon className="px-1" icon={menuIcon} />}
|
||||||
|
size="sm"
|
||||||
|
minWidth={minWidth}
|
||||||
|
end
|
||||||
|
noCaret
|
||||||
|
inline
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DropdownBtn>
|
||||||
|
);
|
||||||
@@ -22,7 +22,7 @@ export const VisitsFilterDropdown = (
|
|||||||
const onBotsClick = () => onChange({ ...selected, excludeBots: !selected?.excludeBots });
|
const onBotsClick = () => onChange({ ...selected, excludeBots: !selected?.excludeBots });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownBtn text="Filters" dropdownClassName={className} className="me-3" right minWidth={250}>
|
<DropdownBtn text="Filters" dropdownClassName={className} inline end minWidth={250}>
|
||||||
<DropdownItem header>Bots:</DropdownItem>
|
<DropdownItem header>Bots:</DropdownItem>
|
||||||
<DropdownItem active={excludeBots} onClick={onBotsClick}>Exclude potential bots</DropdownItem>
|
<DropdownItem active={excludeBots} onClick={onBotsClick}>Exclude potential bots</DropdownItem>
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
const jestConfig = require(`${__dirname}/jest.config.js`);
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
mutate: jestConfig.collectCoverageFrom,
|
|
||||||
checkers: [ 'typescript' ],
|
|
||||||
tsconfigFile: 'tsconfig.json',
|
|
||||||
testRunner: 'jest',
|
|
||||||
reporters: [ 'progress', 'clear-text' ],
|
|
||||||
ignorePatterns: [
|
|
||||||
'coverage',
|
|
||||||
'reports',
|
|
||||||
'build',
|
|
||||||
'dist',
|
|
||||||
'home',
|
|
||||||
'scripts',
|
|
||||||
'docker-compose.*',
|
|
||||||
'public/servers.json*',
|
|
||||||
],
|
|
||||||
jest: {
|
|
||||||
projectType: 'custom',
|
|
||||||
config: jestConfig,
|
|
||||||
enableFindRelatedTests: true,
|
|
||||||
},
|
|
||||||
thresholds: {
|
|
||||||
high: 80,
|
|
||||||
low: 60,
|
|
||||||
break: null,
|
|
||||||
},
|
|
||||||
clearTextReporter: {
|
|
||||||
logTests: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -5,7 +5,11 @@ import type { ReactElement } from 'react';
|
|||||||
export const setUpCanvas = (element: ReactElement) => {
|
export const setUpCanvas = (element: ReactElement) => {
|
||||||
const result = render(element);
|
const result = render(element);
|
||||||
const { container } = result;
|
const { container } = result;
|
||||||
const getEvents = () => container.querySelector('canvas')?.getContext('2d')?.__getEvents(); // eslint-disable-line no-underscore-dangle
|
const getEvents = () => {
|
||||||
|
const context = container.querySelector('canvas')?.getContext('2d');
|
||||||
|
// @ts-expect-error __getEvents is set by vitest-canvas-mock
|
||||||
|
return context?.__getEvents(); // eslint-disable-line no-underscore-dangle
|
||||||
|
};
|
||||||
|
|
||||||
return { ...result, events: getEvents(), getEvents };
|
return { ...result, events: getEvents(), getEvents };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { fromAny, fromPartial } from '@total-typescript/shoehorn';
|
||||||
|
|
||||||
const createLinkMock = () => ({
|
const createLinkMock = () => ({
|
||||||
setAttribute: jest.fn(),
|
setAttribute: vi.fn(),
|
||||||
click: jest.fn(),
|
click: vi.fn(),
|
||||||
style: {},
|
style: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const appendChild = jest.fn();
|
export const appendChild = vi.fn();
|
||||||
|
|
||||||
export const removeChild = jest.fn();
|
export const removeChild = vi.fn();
|
||||||
|
|
||||||
export const windowMock = Mock.of<Window>({
|
export const windowMock = fromPartial<Window>({
|
||||||
document: {
|
document: fromAny({
|
||||||
createElement: jest.fn(createLinkMock),
|
createElement: vi.fn(createLinkMock),
|
||||||
body: { appendChild, removeChild },
|
body: { appendChild, removeChild },
|
||||||
},
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { ShlinkApiErrorProps } from '../../src/api/ShlinkApiError';
|
import type { ShlinkApiErrorProps } from '../../src/api/ShlinkApiError';
|
||||||
import { ShlinkApiError } from '../../src/api/ShlinkApiError';
|
import { ShlinkApiError } from '../../src/api/ShlinkApiError';
|
||||||
import type { InvalidArgumentError, ProblemDetailsError } from '../../src/api/types/errors';
|
import type { InvalidArgumentError, ProblemDetailsError } from '../../src/api/types/errors';
|
||||||
@@ -10,8 +10,8 @@ describe('<ShlinkApiError />', () => {
|
|||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[undefined, 'the fallback', 'the fallback'],
|
[undefined, 'the fallback', 'the fallback'],
|
||||||
[Mock.all<ProblemDetailsError>(), 'the fallback', 'the fallback'],
|
[fromPartial<ProblemDetailsError>({}), 'the fallback', 'the fallback'],
|
||||||
[Mock.of<ProblemDetailsError>({ detail: 'the detail' }), 'the fallback', 'the detail'],
|
[fromPartial<ProblemDetailsError>({ detail: 'the detail' }), 'the fallback', 'the detail'],
|
||||||
])('renders proper message', (errorData, fallbackMessage, expectedMessage) => {
|
])('renders proper message', (errorData, fallbackMessage, expectedMessage) => {
|
||||||
const { container } = setUp({ errorData, fallbackMessage });
|
const { container } = setUp({ errorData, fallbackMessage });
|
||||||
|
|
||||||
@@ -21,9 +21,9 @@ describe('<ShlinkApiError />', () => {
|
|||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[undefined, 0],
|
[undefined, 0],
|
||||||
[Mock.all<ProblemDetailsError>(), 0],
|
[fromPartial<ProblemDetailsError>({}), 0],
|
||||||
[Mock.of<InvalidArgumentError>({ type: ErrorTypeV2.INVALID_ARGUMENT, invalidElements: [] }), 1],
|
[fromPartial<InvalidArgumentError>({ type: ErrorTypeV2.INVALID_ARGUMENT, invalidElements: [] }), 1],
|
||||||
[Mock.of<InvalidArgumentError>({ type: ErrorTypeV3.INVALID_ARGUMENT, invalidElements: [] }), 1],
|
[fromPartial<InvalidArgumentError>({ type: ErrorTypeV3.INVALID_ARGUMENT, invalidElements: [] }), 1],
|
||||||
])('renders list of invalid elements when provided error is an InvalidError', (errorData, expectedElementsCount) => {
|
])('renders list of invalid elements when provided error is an InvalidError', (errorData, expectedElementsCount) => {
|
||||||
setUp({ errorData });
|
setUp({ errorData });
|
||||||
expect(screen.queryAllByText(/^Invalid elements/)).toHaveLength(expectedElementsCount);
|
expect(screen.queryAllByText(/^Invalid elements/)).toHaveLength(expectedElementsCount);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient';
|
import { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient';
|
||||||
import type { ShlinkDomain, ShlinkVisits, ShlinkVisitsOverview } from '../../../src/api/types';
|
import type { ShlinkDomain, ShlinkVisits, ShlinkVisitsOverview } from '../../../src/api/types';
|
||||||
import { ErrorTypeV2, ErrorTypeV3 } from '../../../src/api/types/errors';
|
import { ErrorTypeV2, ErrorTypeV3 } from '../../../src/api/types/errors';
|
||||||
@@ -7,9 +7,9 @@ import type { ShortUrl, ShortUrlsOrder } from '../../../src/short-urls/data';
|
|||||||
import type { OptionalString } from '../../../src/utils/utils';
|
import type { OptionalString } from '../../../src/utils/utils';
|
||||||
|
|
||||||
describe('ShlinkApiClient', () => {
|
describe('ShlinkApiClient', () => {
|
||||||
const fetchJson = jest.fn().mockResolvedValue({});
|
const fetchJson = vi.fn().mockResolvedValue({});
|
||||||
const fetchEmpty = jest.fn().mockResolvedValue(undefined);
|
const fetchEmpty = vi.fn().mockResolvedValue(undefined);
|
||||||
const httpClient = Mock.of<HttpClient>({ fetchJson, fetchEmpty });
|
const httpClient = fromPartial<HttpClient>({ fetchJson, fetchEmpty });
|
||||||
const buildApiClient = () => new ShlinkApiClient(httpClient, '', '');
|
const buildApiClient = () => new ShlinkApiClient(httpClient, '', '');
|
||||||
const shortCodesWithDomainCombinations: [string, OptionalString][] = [
|
const shortCodesWithDomainCombinations: [string, OptionalString][] = [
|
||||||
['abc123', null],
|
['abc123', null],
|
||||||
@@ -17,8 +17,6 @@ describe('ShlinkApiClient', () => {
|
|||||||
['abc123', 'example.com'],
|
['abc123', 'example.com'],
|
||||||
];
|
];
|
||||||
|
|
||||||
beforeEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
describe('listShortUrls', () => {
|
describe('listShortUrls', () => {
|
||||||
const expectedList = ['foo', 'bar'];
|
const expectedList = ['foo', 'bar'];
|
||||||
|
|
||||||
@@ -177,7 +175,7 @@ describe('ShlinkApiClient', () => {
|
|||||||
maxVisits: 50,
|
maxVisits: 50,
|
||||||
validSince: '2025-01-01T10:00:00+01:00',
|
validSince: '2025-01-01T10:00:00+01:00',
|
||||||
};
|
};
|
||||||
const expectedResp = Mock.of<ShortUrl>();
|
const expectedResp = fromPartial<ShortUrl>({});
|
||||||
fetchJson.mockResolvedValue(expectedResp);
|
fetchJson.mockResolvedValue(expectedResp);
|
||||||
const { updateShortUrl } = buildApiClient();
|
const { updateShortUrl } = buildApiClient();
|
||||||
const expectedQuery = domain ? `?domain=${domain}` : '';
|
const expectedQuery = domain ? `?domain=${domain}` : '';
|
||||||
@@ -311,7 +309,7 @@ describe('ShlinkApiClient', () => {
|
|||||||
|
|
||||||
describe('listDomains', () => {
|
describe('listDomains', () => {
|
||||||
it('returns domains', async () => {
|
it('returns domains', async () => {
|
||||||
const expectedData = { data: [Mock.all<ShlinkDomain>(), Mock.all<ShlinkDomain>()] };
|
const expectedData = { data: [fromPartial<ShlinkDomain>({}), fromPartial<ShlinkDomain>({})] };
|
||||||
fetchJson.mockResolvedValue({ domains: expectedData });
|
fetchJson.mockResolvedValue({ domains: expectedData });
|
||||||
const { listDomains } = buildApiClient();
|
const { listDomains } = buildApiClient();
|
||||||
|
|
||||||
@@ -324,7 +322,7 @@ describe('ShlinkApiClient', () => {
|
|||||||
|
|
||||||
describe('getVisitsOverview', () => {
|
describe('getVisitsOverview', () => {
|
||||||
it('returns visits overview', async () => {
|
it('returns visits overview', async () => {
|
||||||
const expectedData = Mock.all<ShlinkVisitsOverview>();
|
const expectedData = fromPartial<ShlinkVisitsOverview>({});
|
||||||
fetchJson.mockResolvedValue({ visits: expectedData });
|
fetchJson.mockResolvedValue({ visits: expectedData });
|
||||||
const { getVisitsOverview } = buildApiClient();
|
const { getVisitsOverview } = buildApiClient();
|
||||||
|
|
||||||
@@ -337,7 +335,7 @@ describe('ShlinkApiClient', () => {
|
|||||||
|
|
||||||
describe('getOrphanVisits', () => {
|
describe('getOrphanVisits', () => {
|
||||||
it('returns orphan visits', async () => {
|
it('returns orphan visits', async () => {
|
||||||
fetchJson.mockResolvedValue({ visits: Mock.of<ShlinkVisits>({ data: [] }) });
|
fetchJson.mockResolvedValue({ visits: fromPartial<ShlinkVisits>({ data: [] }) });
|
||||||
const { getOrphanVisits } = buildApiClient();
|
const { getOrphanVisits } = buildApiClient();
|
||||||
|
|
||||||
const result = await getOrphanVisits();
|
const result = await getOrphanVisits();
|
||||||
@@ -349,7 +347,7 @@ describe('ShlinkApiClient', () => {
|
|||||||
|
|
||||||
describe('getNonOrphanVisits', () => {
|
describe('getNonOrphanVisits', () => {
|
||||||
it('returns non-orphan visits', async () => {
|
it('returns non-orphan visits', async () => {
|
||||||
fetchJson.mockResolvedValue({ visits: Mock.of<ShlinkVisits>({ data: [] }) });
|
fetchJson.mockResolvedValue({ visits: fromPartial<ShlinkVisits>({ data: [] }) });
|
||||||
const { getNonOrphanVisits } = buildApiClient();
|
const { getNonOrphanVisits } = buildApiClient();
|
||||||
|
|
||||||
const result = await getNonOrphanVisits();
|
const result = await getNonOrphanVisits();
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { buildShlinkApiClient } from '../../../src/api/services/ShlinkApiClientBuilder';
|
import { buildShlinkApiClient } from '../../../src/api/services/ShlinkApiClientBuilder';
|
||||||
import type { HttpClient } from '../../../src/common/services/HttpClient';
|
|
||||||
import type { ShlinkState } from '../../../src/container/types';
|
|
||||||
import type { ReachableServer, SelectedServer } from '../../../src/servers/data';
|
import type { ReachableServer, SelectedServer } from '../../../src/servers/data';
|
||||||
|
|
||||||
describe('ShlinkApiClientBuilder', () => {
|
describe('ShlinkApiClientBuilder', () => {
|
||||||
const server = (data: Partial<ReachableServer>) => Mock.of<ReachableServer>(data);
|
const server = fromPartial<ReachableServer>;
|
||||||
|
|
||||||
const createBuilder = () => {
|
const createBuilder = () => {
|
||||||
const builder = buildShlinkApiClient(Mock.of<HttpClient>());
|
const builder = buildShlinkApiClient(fromPartial({}));
|
||||||
return (selectedServer: SelectedServer) => builder(() => Mock.of<ShlinkState>({ selectedServer }));
|
return (selectedServer: SelectedServer) => builder(() => fromPartial({ selectedServer }));
|
||||||
};
|
};
|
||||||
|
|
||||||
it('creates new instances when provided params are different', async () => {
|
it('creates new instances when provided params are different', async () => {
|
||||||
@@ -42,7 +40,7 @@ describe('ShlinkApiClientBuilder', () => {
|
|||||||
it('does not fetch from state when provided param is already selected server', () => {
|
it('does not fetch from state when provided param is already selected server', () => {
|
||||||
const url = 'url';
|
const url = 'url';
|
||||||
const apiKey = 'apiKey';
|
const apiKey = 'apiKey';
|
||||||
const apiClient = buildShlinkApiClient(Mock.of<HttpClient>())(server({ url, apiKey }));
|
const apiClient = buildShlinkApiClient(fromPartial({}))(server({ url, apiKey }));
|
||||||
|
|
||||||
expect(apiClient['baseUrl']).toEqual(url); // eslint-disable-line @typescript-eslint/dot-notation
|
expect(apiClient['baseUrl']).toEqual(url); // eslint-disable-line @typescript-eslint/dot-notation
|
||||||
expect(apiClient['apiKey']).toEqual(apiKey); // eslint-disable-line @typescript-eslint/dot-notation
|
expect(apiClient['apiKey']).toEqual(apiKey); // eslint-disable-line @typescript-eslint/dot-notation
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { createMemoryHistory } from 'history';
|
import { createMemoryHistory } from 'history';
|
||||||
import { Router } from 'react-router-dom';
|
import { Router } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import { App as createApp } from '../../src/app/App';
|
import { App as createApp } from '../../src/app/App';
|
||||||
import type { Settings } from '../../src/settings/reducers/settings';
|
|
||||||
|
|
||||||
describe('<App />', () => {
|
describe('<App />', () => {
|
||||||
const App = createApp(
|
const App = createApp(
|
||||||
@@ -25,7 +24,7 @@ describe('<App />', () => {
|
|||||||
<App
|
<App
|
||||||
fetchServers={() => {}}
|
fetchServers={() => {}}
|
||||||
servers={{}}
|
servers={{}}
|
||||||
settings={Mock.all<Settings>()}
|
settings={fromPartial({})}
|
||||||
appUpdated
|
appUpdated
|
||||||
resetAppUpdate={() => {}}
|
resetAppUpdate={() => {}}
|
||||||
/>
|
/>
|
||||||
@@ -33,8 +32,6 @@ describe('<App />', () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('renders children components', () => {
|
it('renders children components', () => {
|
||||||
setUp();
|
setUp();
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,10 @@ import { AppUpdateBanner } from '../../src/common/AppUpdateBanner';
|
|||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<AppUpdateBanner />', () => {
|
describe('<AppUpdateBanner />', () => {
|
||||||
const toggle = jest.fn();
|
const toggle = vi.fn();
|
||||||
const forceUpdate = jest.fn();
|
const forceUpdate = vi.fn();
|
||||||
const setUp = () => renderWithEvents(<AppUpdateBanner isOpen toggle={toggle} forceUpdate={forceUpdate} />);
|
const setUp = () => renderWithEvents(<AppUpdateBanner isOpen toggle={toggle} forceUpdate={forceUpdate} />);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('renders initial state', () => {
|
it('renders initial state', () => {
|
||||||
setUp();
|
setUp();
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { Mock } from 'ts-mockery';
|
import { MemoryRouter } from 'react-router';
|
||||||
import { AsideMenu as createAsideMenu } from '../../src/common/AsideMenu';
|
import { AsideMenu as createAsideMenu } from '../../src/common/AsideMenu';
|
||||||
import type { ReachableServer } from '../../src/servers/data';
|
|
||||||
|
|
||||||
describe('<AsideMenu />', () => {
|
describe('<AsideMenu />', () => {
|
||||||
const AsideMenu = createAsideMenu(() => <>DeleteServerButton</>);
|
const AsideMenu = createAsideMenu(() => <>DeleteServerButton</>);
|
||||||
const setUp = (id: string | false = 'abc123') => render(
|
const setUp = (id: string | false = 'abc123') => render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<AsideMenu selectedServer={Mock.of<ReachableServer>({ id: id || undefined, version: '2.8.0' })} />
|
<AsideMenu selectedServer={fromPartial({ id: id || undefined, version: '2.8.0' })} />
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { ErrorHandler as createErrorHandler } from '../../src/common/ErrorHandler';
|
import { ErrorHandler as createErrorHandler } from '../../src/common/ErrorHandler';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
@@ -8,17 +8,16 @@ const ComponentWithError = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('<ErrorHandler />', () => {
|
describe('<ErrorHandler />', () => {
|
||||||
const reload = jest.fn();
|
const reload = vi.fn();
|
||||||
const window = Mock.of<Window>({
|
const window = fromPartial<Window>({
|
||||||
location: { reload },
|
location: { reload },
|
||||||
});
|
});
|
||||||
const cons = Mock.of<Console>({ error: jest.fn() });
|
const cons = fromPartial<Console>({ error: vi.fn() });
|
||||||
const ErrorHandler = createErrorHandler(window, cons);
|
const ErrorHandler = createErrorHandler(window, cons);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.spyOn(console, 'error').mockImplementation(() => {}); // Silence react errors
|
vi.spyOn(console, 'error').mockImplementation(() => {}); // Silence react errors
|
||||||
});
|
});
|
||||||
afterEach(jest.resetAllMocks);
|
|
||||||
|
|
||||||
it('renders children when no error has occurred', () => {
|
it('renders children when no error has occurred', () => {
|
||||||
render(<ErrorHandler children={<span>Foo</span>} />);
|
render(<ErrorHandler children={<span>Foo</span>} />);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { Mock } from 'ts-mockery';
|
import { MemoryRouter } from 'react-router';
|
||||||
import { Home } from '../../src/common/Home';
|
import { Home } from '../../src/common/Home';
|
||||||
import type { ServersMap, ServerWithId } from '../../src/servers/data';
|
import type { ServersMap, ServerWithId } from '../../src/servers/data';
|
||||||
|
|
||||||
@@ -19,9 +19,9 @@ describe('<Home />', () => {
|
|||||||
it.each([
|
it.each([
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
'1a': Mock.of<ServerWithId>({ name: 'foo', id: '1' }),
|
'1a': fromPartial<ServerWithId>({ name: 'foo', id: '1' }),
|
||||||
'2b': Mock.of<ServerWithId>({ name: 'bar', id: '2' }),
|
'2b': fromPartial<ServerWithId>({ name: 'bar', id: '2' }),
|
||||||
'3c': Mock.of<ServerWithId>({ name: 'baz', id: '3' }),
|
'3c': fromPartial<ServerWithId>({ name: 'baz', id: '3' }),
|
||||||
},
|
},
|
||||||
3,
|
3,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { createMemoryHistory } from 'history';
|
import { createMemoryHistory } from 'history';
|
||||||
import { Router, useParams } from 'react-router-dom';
|
import { Router, useParams } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import { MenuLayout as createMenuLayout } from '../../src/common/MenuLayout';
|
import { MenuLayout as createMenuLayout } from '../../src/common/MenuLayout';
|
||||||
import type { NonReachableServer, NotFoundServer, ReachableServer, SelectedServer } from '../../src/servers/data';
|
import type { NonReachableServer, NotFoundServer, SelectedServer } from '../../src/servers/data';
|
||||||
import type { SemVer } from '../../src/utils/helpers/version';
|
import type { SemVer } from '../../src/utils/helpers/version';
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useParams: jest.fn() }));
|
vi.mock('react-router-dom', async () => ({ ...(await vi.importActual<any>('react-router-dom')), useParams: vi.fn() }));
|
||||||
|
|
||||||
describe('<MenuLayout />', () => {
|
describe('<MenuLayout />', () => {
|
||||||
const MenuLayout = createMenuLayout(
|
const MenuLayout = createMenuLayout(
|
||||||
@@ -31,9 +31,9 @@ describe('<MenuLayout />', () => {
|
|||||||
return render(
|
return render(
|
||||||
<Router location={history.location} navigator={history}>
|
<Router location={history.location} navigator={history}>
|
||||||
<MenuLayout
|
<MenuLayout
|
||||||
sidebarNotPresent={jest.fn()}
|
sidebarNotPresent={vi.fn()}
|
||||||
sidebarPresent={jest.fn()}
|
sidebarPresent={vi.fn()}
|
||||||
selectServer={jest.fn()}
|
selectServer={vi.fn()}
|
||||||
selectedServer={selectedServer}
|
selectedServer={selectedServer}
|
||||||
/>
|
/>
|
||||||
</Router>,
|
</Router>,
|
||||||
@@ -44,8 +44,6 @@ describe('<MenuLayout />', () => {
|
|||||||
(useParams as any).mockReturnValue({ serverId: 'abc123' });
|
(useParams as any).mockReturnValue({ serverId: 'abc123' });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('shows loading indicator while loading server', () => {
|
it('shows loading indicator while loading server', () => {
|
||||||
setUp(null);
|
setUp(null);
|
||||||
|
|
||||||
@@ -54,8 +52,8 @@ describe('<MenuLayout />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[Mock.of<NotFoundServer>({ serverNotFound: true })],
|
[fromPartial<NotFoundServer>({ serverNotFound: true })],
|
||||||
[Mock.of<NonReachableServer>({ serverNotReachable: true })],
|
[fromPartial<NonReachableServer>({ serverNotReachable: true })],
|
||||||
])('shows error for non reachable servers', (selectedServer) => {
|
])('shows error for non reachable servers', (selectedServer) => {
|
||||||
setUp(selectedServer);
|
setUp(selectedServer);
|
||||||
|
|
||||||
@@ -81,7 +79,7 @@ describe('<MenuLayout />', () => {
|
|||||||
])(
|
])(
|
||||||
'renders expected component based on location and server version',
|
'renders expected component based on location and server version',
|
||||||
(version, currentPath, expectedContent) => {
|
(version, currentPath, expectedContent) => {
|
||||||
setUp(Mock.of<ReachableServer>({ version }), currentPath);
|
setUp(fromPartial({ version }), currentPath);
|
||||||
expect(screen.getByText(expectedContent)).toBeInTheDocument();
|
expect(screen.getByText(expectedContent)).toBeInTheDocument();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router';
|
||||||
import { NotFound } from '../../src/common/NotFound';
|
import { NotFound } from '../../src/common/NotFound';
|
||||||
|
|
||||||
describe('<NotFound />', () => {
|
describe('<NotFound />', () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router';
|
||||||
import { ScrollToTop } from '../../src/common/ScrollToTop';
|
import { ScrollToTop } from '../../src/common/ScrollToTop';
|
||||||
|
|
||||||
describe('<ScrollToTop />', () => {
|
describe('<ScrollToTop />', () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { ShlinkVersionsProps } from '../../src/common/ShlinkVersions';
|
import type { ShlinkVersionsProps } from '../../src/common/ShlinkVersions';
|
||||||
import { ShlinkVersions } from '../../src/common/ShlinkVersions';
|
import { ShlinkVersions } from '../../src/common/ShlinkVersions';
|
||||||
import type { NonReachableServer, NotFoundServer, ReachableServer } from '../../src/servers/data';
|
import type { NonReachableServer, NotFoundServer, ReachableServer } from '../../src/servers/data';
|
||||||
@@ -8,11 +8,11 @@ describe('<ShlinkVersions />', () => {
|
|||||||
const setUp = (props: ShlinkVersionsProps) => render(<ShlinkVersions {...props} />);
|
const setUp = (props: ShlinkVersionsProps) => render(<ShlinkVersions {...props} />);
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
['1.2.3', Mock.of<ReachableServer>({ version: '1.0.0', printableVersion: 'foo' }), 'v1.2.3', 'foo'],
|
['1.2.3', fromPartial<ReachableServer>({ version: '1.0.0', printableVersion: 'foo' }), 'v1.2.3', 'foo'],
|
||||||
['foo', Mock.of<ReachableServer>({ version: '1.0.0', printableVersion: '1.2.3' }), 'latest', '1.2.3'],
|
['foo', fromPartial<ReachableServer>({ version: '1.0.0', printableVersion: '1.2.3' }), 'latest', '1.2.3'],
|
||||||
['latest', Mock.of<ReachableServer>({ version: '1.0.0', printableVersion: 'latest' }), 'latest', 'latest'],
|
['latest', fromPartial<ReachableServer>({ version: '1.0.0', printableVersion: 'latest' }), 'latest', 'latest'],
|
||||||
['5.5.0', Mock.of<ReachableServer>({ version: '1.0.0', printableVersion: '0.2.8' }), 'v5.5.0', '0.2.8'],
|
['5.5.0', fromPartial<ReachableServer>({ version: '1.0.0', printableVersion: '0.2.8' }), 'v5.5.0', '0.2.8'],
|
||||||
['not-semver', Mock.of<ReachableServer>({ version: '1.0.0', printableVersion: 'some' }), 'latest', 'some'],
|
['not-semver', fromPartial<ReachableServer>({ version: '1.0.0', printableVersion: 'some' }), 'latest', 'some'],
|
||||||
])(
|
])(
|
||||||
'displays expected versions when selected server is reachable',
|
'displays expected versions when selected server is reachable',
|
||||||
(clientVersion, selectedServer, expectedClientVersion, expectedServerVersion) => {
|
(clientVersion, selectedServer, expectedClientVersion, expectedServerVersion) => {
|
||||||
@@ -34,8 +34,8 @@ describe('<ShlinkVersions />', () => {
|
|||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
['1.2.3', null],
|
['1.2.3', null],
|
||||||
['1.2.3', Mock.of<NotFoundServer>({ serverNotFound: true })],
|
['1.2.3', fromPartial<NotFoundServer>({ serverNotFound: true })],
|
||||||
['1.2.3', Mock.of<NonReachableServer>({ serverNotReachable: true })],
|
['1.2.3', fromPartial<NonReachableServer>({ serverNotReachable: true })],
|
||||||
])('displays only client version when selected server is not reachable', (clientVersion, selectedServer) => {
|
])('displays only client version when selected server is not reachable', (clientVersion, selectedServer) => {
|
||||||
setUp({ clientVersion, selectedServer });
|
setUp({ clientVersion, selectedServer });
|
||||||
const links = screen.getAllByRole('link');
|
const links = screen.getAllByRole('link');
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { Sidebar } from '../../src/common/reducers/sidebar';
|
import type { Sidebar } from '../../src/common/reducers/sidebar';
|
||||||
import { ShlinkVersionsContainer } from '../../src/common/ShlinkVersionsContainer';
|
import { ShlinkVersionsContainer } from '../../src/common/ShlinkVersionsContainer';
|
||||||
import type { SelectedServer } from '../../src/servers/data';
|
|
||||||
|
|
||||||
describe('<ShlinkVersionsContainer />', () => {
|
describe('<ShlinkVersionsContainer />', () => {
|
||||||
const setUp = (sidebar: Sidebar) => render(
|
const setUp = (sidebar: Sidebar) => render(
|
||||||
<ShlinkVersionsContainer selectedServer={Mock.all<SelectedServer>()} sidebar={sidebar} />,
|
<ShlinkVersionsContainer selectedServer={fromPartial({})} sidebar={sidebar} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ELLIPSIS } from '../../src/utils/helpers/pagination';
|
|||||||
|
|
||||||
describe('<SimplePaginator />', () => {
|
describe('<SimplePaginator />', () => {
|
||||||
const setUp = (pagesCount: number, currentPage = 1) => render(
|
const setUp = (pagesCount: number, currentPage = 1) => render(
|
||||||
<SimplePaginator pagesCount={pagesCount} currentPage={currentPage} setCurrentPage={jest.fn()} />,
|
<SimplePaginator pagesCount={pagesCount} currentPage={currentPage} setCurrentPage={vi.fn()} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
it.each([-3, -2, 0, 1])('renders empty when the amount of pages is smaller than 2', (pagesCount) => {
|
it.each([-3, -2, 0, 1])('renders empty when the amount of pages is smaller than 2', (pagesCount) => {
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { HttpClient } from '../../../src/common/services/HttpClient';
|
import { HttpClient } from '../../../src/common/services/HttpClient';
|
||||||
|
|
||||||
describe('HttpClient', () => {
|
describe('HttpClient', () => {
|
||||||
const fetch = jest.fn();
|
const fetch = vi.fn();
|
||||||
const httpClient = new HttpClient(fetch);
|
const httpClient = new HttpClient(fetch);
|
||||||
|
|
||||||
beforeEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
describe('fetchJson', () => {
|
describe('fetchJson', () => {
|
||||||
it('throws json on success', async () => {
|
it('throws json on success', async () => {
|
||||||
const theError = { error: true, foo: 'bar' };
|
const theError = { error: true, foo: 'bar' };
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { HttpClient } from '../../../src/common/services/HttpClient';
|
import type { HttpClient } from '../../../src/common/services/HttpClient';
|
||||||
import { ImageDownloader } from '../../../src/common/services/ImageDownloader';
|
import { ImageDownloader } from '../../../src/common/services/ImageDownloader';
|
||||||
import { windowMock } from '../../__mocks__/Window.mock';
|
import { windowMock } from '../../__mocks__/Window.mock';
|
||||||
|
|
||||||
describe('ImageDownloader', () => {
|
describe('ImageDownloader', () => {
|
||||||
const fetchBlob = jest.fn();
|
const fetchBlob = vi.fn();
|
||||||
const httpClient = Mock.of<HttpClient>({ fetchBlob });
|
const httpClient = fromPartial<HttpClient>({ fetchBlob });
|
||||||
let imageDownloader: ImageDownloader;
|
let imageDownloader: ImageDownloader;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
|
||||||
(global as any).URL = { createObjectURL: () => '' };
|
(global as any).URL = { createObjectURL: () => '' };
|
||||||
|
|
||||||
imageDownloader = new ImageDownloader(httpClient, windowMock);
|
imageDownloader = new ImageDownloader(httpClient, windowMock);
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ import type { NormalizedVisit } from '../../../src/visits/types';
|
|||||||
import { windowMock } from '../../__mocks__/Window.mock';
|
import { windowMock } from '../../__mocks__/Window.mock';
|
||||||
|
|
||||||
describe('ReportExporter', () => {
|
describe('ReportExporter', () => {
|
||||||
const jsonToCsv = jest.fn();
|
const jsonToCsv = vi.fn();
|
||||||
let exporter: ReportExporter;
|
let exporter: ReportExporter;
|
||||||
|
|
||||||
beforeEach(jest.clearAllMocks);
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(global as any).Blob = class Blob {};
|
(global as any).Blob = class Blob {};
|
||||||
(global as any).URL = { createObjectURL: () => '' };
|
(global as any).URL = { createObjectURL: () => '' };
|
||||||
@@ -53,6 +52,7 @@ describe('ReportExporter', () => {
|
|||||||
createdAt: '',
|
createdAt: '',
|
||||||
longUrl: '',
|
longUrl: '',
|
||||||
tags: '',
|
tags: '',
|
||||||
|
shortCode: '',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { ShlinkDomainRedirects } from '../../src/api/types';
|
import type { ShlinkDomainRedirects } from '../../src/api/types';
|
||||||
import type { Domain } from '../../src/domains/data';
|
import type { Domain } from '../../src/domains/data';
|
||||||
import { DomainRow } from '../../src/domains/DomainRow';
|
import { DomainRow } from '../../src/domains/DomainRow';
|
||||||
import type { SelectedServer } from '../../src/servers/data';
|
|
||||||
|
|
||||||
describe('<DomainRow />', () => {
|
describe('<DomainRow />', () => {
|
||||||
const redirectsCombinations = [
|
const redirectsCombinations = [
|
||||||
[Mock.of<ShlinkDomainRedirects>({ baseUrlRedirect: 'foo' })],
|
[fromPartial<ShlinkDomainRedirects>({ baseUrlRedirect: 'foo' })],
|
||||||
[Mock.of<ShlinkDomainRedirects>({ invalidShortUrlRedirect: 'bar' })],
|
[fromPartial<ShlinkDomainRedirects>({ invalidShortUrlRedirect: 'bar' })],
|
||||||
[Mock.of<ShlinkDomainRedirects>({ baseUrlRedirect: 'baz', regular404Redirect: 'foo' })],
|
[fromPartial<ShlinkDomainRedirects>({ baseUrlRedirect: 'baz', regular404Redirect: 'foo' })],
|
||||||
[
|
[
|
||||||
Mock.of<ShlinkDomainRedirects>(
|
fromPartial<ShlinkDomainRedirects>(
|
||||||
{ baseUrlRedirect: 'baz', regular404Redirect: 'bar', invalidShortUrlRedirect: 'foo' },
|
{ baseUrlRedirect: 'baz', regular404Redirect: 'bar', invalidShortUrlRedirect: 'foo' },
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -22,16 +21,16 @@ describe('<DomainRow />', () => {
|
|||||||
<DomainRow
|
<DomainRow
|
||||||
domain={domain}
|
domain={domain}
|
||||||
defaultRedirects={defaultRedirects}
|
defaultRedirects={defaultRedirects}
|
||||||
selectedServer={Mock.all<SelectedServer>()}
|
selectedServer={fromPartial({})}
|
||||||
editDomainRedirects={jest.fn()}
|
editDomainRedirects={vi.fn()}
|
||||||
checkDomainHealth={jest.fn()}
|
checkDomainHealth={vi.fn()}
|
||||||
/>
|
/>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>,
|
</table>,
|
||||||
);
|
);
|
||||||
|
|
||||||
it.each(redirectsCombinations)('shows expected redirects', (redirects) => {
|
it.each(redirectsCombinations)('shows expected redirects', (redirects) => {
|
||||||
setUp(Mock.of<Domain>({ domain: '', isDefault: true, redirects }));
|
setUp(fromPartial({ domain: '', isDefault: true, redirects }));
|
||||||
const cells = screen.getAllByRole('cell');
|
const cells = screen.getAllByRole('cell');
|
||||||
|
|
||||||
redirects?.baseUrlRedirect && expect(cells[1]).toHaveTextContent(redirects.baseUrlRedirect);
|
redirects?.baseUrlRedirect && expect(cells[1]).toHaveTextContent(redirects.baseUrlRedirect);
|
||||||
@@ -42,9 +41,9 @@ describe('<DomainRow />', () => {
|
|||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[undefined],
|
[undefined],
|
||||||
[Mock.of<ShlinkDomainRedirects>()],
|
[fromPartial<ShlinkDomainRedirects>({})],
|
||||||
])('shows expected "no redirects"', (redirects) => {
|
])('shows expected "no redirects"', (redirects) => {
|
||||||
setUp(Mock.of<Domain>({ domain: '', isDefault: true, redirects }));
|
setUp(fromPartial({ domain: '', isDefault: true, redirects }));
|
||||||
const cells = screen.getAllByRole('cell');
|
const cells = screen.getAllByRole('cell');
|
||||||
|
|
||||||
expect(cells[1]).toHaveTextContent('No redirect');
|
expect(cells[1]).toHaveTextContent('No redirect');
|
||||||
@@ -54,7 +53,7 @@ describe('<DomainRow />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each(redirectsCombinations)('shows expected fallback redirects', (fallbackRedirects) => {
|
it.each(redirectsCombinations)('shows expected fallback redirects', (fallbackRedirects) => {
|
||||||
setUp(Mock.of<Domain>({ domain: '', isDefault: true }), fallbackRedirects);
|
setUp(fromPartial({ domain: '', isDefault: true }), fallbackRedirects);
|
||||||
const cells = screen.getAllByRole('cell');
|
const cells = screen.getAllByRole('cell');
|
||||||
|
|
||||||
fallbackRedirects?.baseUrlRedirect && expect(cells[1]).toHaveTextContent(
|
fallbackRedirects?.baseUrlRedirect && expect(cells[1]).toHaveTextContent(
|
||||||
@@ -69,7 +68,7 @@ describe('<DomainRow />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([[true], [false]])('shows icon on default domain only', (isDefault) => {
|
it.each([[true], [false]])('shows icon on default domain only', (isDefault) => {
|
||||||
const { container } = setUp(Mock.of<Domain>({ domain: '', isDefault }));
|
const { container } = setUp(fromPartial({ domain: '', isDefault }));
|
||||||
|
|
||||||
if (isDefault) {
|
if (isDefault) {
|
||||||
expect(container.querySelector('#defaultDomainIcon')).toBeInTheDocument();
|
expect(container.querySelector('#defaultDomainIcon')).toBeInTheDocument();
|
||||||
|
|||||||
@@ -1,24 +1,21 @@
|
|||||||
import { screen, waitFor } from '@testing-library/react';
|
import { screen, waitFor } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { ShlinkDomain } from '../../src/api/types';
|
|
||||||
import { DomainSelector } from '../../src/domains/DomainSelector';
|
import { DomainSelector } from '../../src/domains/DomainSelector';
|
||||||
import type { DomainsList } from '../../src/domains/reducers/domainsList';
|
import type { DomainsList } from '../../src/domains/reducers/domainsList';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<DomainSelector />', () => {
|
describe('<DomainSelector />', () => {
|
||||||
const domainsList = Mock.of<DomainsList>({
|
const domainsList = fromPartial<DomainsList>({
|
||||||
domains: [
|
domains: [
|
||||||
Mock.of<ShlinkDomain>({ domain: 'default.com', isDefault: true }),
|
fromPartial({ domain: 'default.com', isDefault: true }),
|
||||||
Mock.of<ShlinkDomain>({ domain: 'foo.com' }),
|
fromPartial({ domain: 'foo.com' }),
|
||||||
Mock.of<ShlinkDomain>({ domain: 'bar.com' }),
|
fromPartial({ domain: 'bar.com' }),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const setUp = (value = '') => renderWithEvents(
|
const setUp = (value = '') => renderWithEvents(
|
||||||
<DomainSelector value={value} domainsList={domainsList} listDomains={jest.fn()} onChange={jest.fn()} />,
|
<DomainSelector value={value} domainsList={domainsList} listDomains={vi.fn()} onChange={vi.fn()} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
['', 'Domain', 'domains-dropdown__toggle-btn'],
|
['', 'Domain', 'domains-dropdown__toggle-btn'],
|
||||||
['my-domain.com', 'Domain: my-domain.com', 'domains-dropdown__toggle-btn--active'],
|
['my-domain.com', 'Domain: my-domain.com', 'domains-dropdown__toggle-btn--active'],
|
||||||
@@ -27,9 +24,8 @@ describe('<DomainSelector />', () => {
|
|||||||
const btn = screen.getByRole('button', { name: expectedText });
|
const btn = screen.getByRole('button', { name: expectedText });
|
||||||
|
|
||||||
expect(screen.queryByPlaceholderText('Domain')).not.toBeInTheDocument();
|
expect(screen.queryByPlaceholderText('Domain')).not.toBeInTheDocument();
|
||||||
expect(btn).toHaveAttribute(
|
expect(btn).toHaveClass(
|
||||||
'class',
|
`dropdown-btn__toggle ${expectedClassName} btn-block dropdown-btn__toggle--with-caret dropdown-toggle btn btn-primary`,
|
||||||
`dropdown-btn__toggle btn-block ${expectedClassName} dropdown-toggle btn btn-primary`,
|
|
||||||
);
|
);
|
||||||
await user.click(btn);
|
await user.click(btn);
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,27 @@
|
|||||||
import { screen, waitFor } from '@testing-library/react';
|
import { screen, waitFor } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { ShlinkDomain } from '../../src/api/types';
|
import type { ShlinkDomain } from '../../src/api/types';
|
||||||
import type { ProblemDetailsError } from '../../src/api/types/errors';
|
import type { ProblemDetailsError } from '../../src/api/types/errors';
|
||||||
import { ManageDomains } from '../../src/domains/ManageDomains';
|
import { ManageDomains } from '../../src/domains/ManageDomains';
|
||||||
import type { DomainsList } from '../../src/domains/reducers/domainsList';
|
import type { DomainsList } from '../../src/domains/reducers/domainsList';
|
||||||
import type { SelectedServer } from '../../src/servers/data';
|
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<ManageDomains />', () => {
|
describe('<ManageDomains />', () => {
|
||||||
const listDomains = jest.fn();
|
const listDomains = vi.fn();
|
||||||
const filterDomains = jest.fn();
|
const filterDomains = vi.fn();
|
||||||
const setUp = (domainsList: DomainsList) => renderWithEvents(
|
const setUp = (domainsList: DomainsList) => renderWithEvents(
|
||||||
<ManageDomains
|
<ManageDomains
|
||||||
listDomains={listDomains}
|
listDomains={listDomains}
|
||||||
filterDomains={filterDomains}
|
filterDomains={filterDomains}
|
||||||
editDomainRedirects={jest.fn()}
|
editDomainRedirects={vi.fn()}
|
||||||
checkDomainHealth={jest.fn()}
|
checkDomainHealth={vi.fn()}
|
||||||
domainsList={domainsList}
|
domainsList={domainsList}
|
||||||
selectedServer={Mock.all<SelectedServer>()}
|
selectedServer={fromPartial({})}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('shows loading message while domains are loading', () => {
|
it('shows loading message while domains are loading', () => {
|
||||||
setUp(Mock.of<DomainsList>({ loading: true, filteredDomains: [] }));
|
setUp(fromPartial({ loading: true, filteredDomains: [] }));
|
||||||
|
|
||||||
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Error loading domains :(')).not.toBeInTheDocument();
|
expect(screen.queryByText('Error loading domains :(')).not.toBeInTheDocument();
|
||||||
@@ -32,17 +29,17 @@ describe('<ManageDomains />', () => {
|
|||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[undefined, 'Error loading domains :('],
|
[undefined, 'Error loading domains :('],
|
||||||
[Mock.of<ProblemDetailsError>(), 'Error loading domains :('],
|
[fromPartial<ProblemDetailsError>({}), 'Error loading domains :('],
|
||||||
[Mock.of<ProblemDetailsError>({ detail: 'Foo error!!' }), 'Foo error!!'],
|
[fromPartial<ProblemDetailsError>({ detail: 'Foo error!!' }), 'Foo error!!'],
|
||||||
])('shows error result when domains loading fails', (errorData, expectedErrorMessage) => {
|
])('shows error result when domains loading fails', (errorData, expectedErrorMessage) => {
|
||||||
setUp(Mock.of<DomainsList>({ loading: false, error: true, errorData, filteredDomains: [] }));
|
setUp(fromPartial({ loading: false, error: true, errorData, filteredDomains: [] }));
|
||||||
|
|
||||||
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
||||||
expect(screen.getByText(expectedErrorMessage)).toBeInTheDocument();
|
expect(screen.getByText(expectedErrorMessage)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('filters domains when SearchField changes', async () => {
|
it('filters domains when SearchField changes', async () => {
|
||||||
const { user } = setUp(Mock.of<DomainsList>({ loading: false, error: false, filteredDomains: [] }));
|
const { user } = setUp(fromPartial({ loading: false, error: false, filteredDomains: [] }));
|
||||||
|
|
||||||
expect(filterDomains).not.toHaveBeenCalled();
|
expect(filterDomains).not.toHaveBeenCalled();
|
||||||
await user.type(screen.getByPlaceholderText('Search...'), 'Foo');
|
await user.type(screen.getByPlaceholderText('Search...'), 'Foo');
|
||||||
@@ -50,19 +47,19 @@ describe('<ManageDomains />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('shows expected headers and one row when list of domains is empty', () => {
|
it('shows expected headers and one row when list of domains is empty', () => {
|
||||||
setUp(Mock.of<DomainsList>({ loading: false, error: false, filteredDomains: [] }));
|
setUp(fromPartial({ loading: false, error: false, filteredDomains: [] }));
|
||||||
|
|
||||||
expect(screen.getAllByRole('columnheader')).toHaveLength(7);
|
expect(screen.getAllByRole('columnheader')).toHaveLength(7);
|
||||||
expect(screen.getByText('No results found')).toBeInTheDocument();
|
expect(screen.getByText('No results found')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has many rows if multiple domains are provided', () => {
|
it('has many rows if multiple domains are provided', () => {
|
||||||
const filteredDomains = [
|
const filteredDomains: ShlinkDomain[] = [
|
||||||
Mock.of<ShlinkDomain>({ domain: 'foo' }),
|
fromPartial({ domain: 'foo' }),
|
||||||
Mock.of<ShlinkDomain>({ domain: 'bar' }),
|
fromPartial({ domain: 'bar' }),
|
||||||
Mock.of<ShlinkDomain>({ domain: 'baz' }),
|
fromPartial({ domain: 'baz' }),
|
||||||
];
|
];
|
||||||
setUp(Mock.of<DomainsList>({ loading: false, error: false, filteredDomains }));
|
setUp(fromPartial({ loading: false, error: false, filteredDomains }));
|
||||||
|
|
||||||
expect(screen.getAllByRole('row')).toHaveLength(filteredDomains.length + 1);
|
expect(screen.getAllByRole('row')).toHaveLength(filteredDomains.length + 1);
|
||||||
expect(screen.getByText('foo')).toBeInTheDocument();
|
expect(screen.getByText('foo')).toBeInTheDocument();
|
||||||
|
|||||||
@@ -1,26 +1,24 @@
|
|||||||
import { screen, waitForElementToBeRemoved } from '@testing-library/react';
|
import { screen, waitForElementToBeRemoved } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import type { Domain } from '../../../src/domains/data';
|
import type { Domain } from '../../../src/domains/data';
|
||||||
import { DomainDropdown } from '../../../src/domains/helpers/DomainDropdown';
|
import { DomainDropdown } from '../../../src/domains/helpers/DomainDropdown';
|
||||||
import type { ReachableServer, SelectedServer } from '../../../src/servers/data';
|
import type { SelectedServer } from '../../../src/servers/data';
|
||||||
import type { SemVer } from '../../../src/utils/helpers/version';
|
import type { SemVer } from '../../../src/utils/helpers/version';
|
||||||
import { renderWithEvents } from '../../__helpers__/setUpTest';
|
import { renderWithEvents } from '../../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<DomainDropdown />', () => {
|
describe('<DomainDropdown />', () => {
|
||||||
const editDomainRedirects = jest.fn().mockResolvedValue(undefined);
|
const editDomainRedirects = vi.fn().mockResolvedValue(undefined);
|
||||||
const setUp = (domain?: Domain, selectedServer?: SelectedServer) => renderWithEvents(
|
const setUp = (domain?: Domain, selectedServer?: SelectedServer) => renderWithEvents(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<DomainDropdown
|
<DomainDropdown
|
||||||
domain={domain ?? Mock.all<Domain>()}
|
domain={domain ?? fromPartial({})}
|
||||||
selectedServer={selectedServer ?? Mock.all<SelectedServer>()}
|
selectedServer={selectedServer ?? fromPartial({})}
|
||||||
editDomainRedirects={editDomainRedirects}
|
editDomainRedirects={editDomainRedirects}
|
||||||
/>
|
/>
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('renders expected menu items', () => {
|
it('renders expected menu items', () => {
|
||||||
setUp();
|
setUp();
|
||||||
|
|
||||||
@@ -33,8 +31,8 @@ describe('<DomainDropdown />', () => {
|
|||||||
[false, ''],
|
[false, ''],
|
||||||
])('points first link to the proper section', (isDefault, expectedLink) => {
|
])('points first link to the proper section', (isDefault, expectedLink) => {
|
||||||
setUp(
|
setUp(
|
||||||
Mock.of<Domain>({ domain: 'foo.com', isDefault }),
|
fromPartial({ domain: 'foo.com', isDefault }),
|
||||||
Mock.of<ReachableServer>({ version: '3.1.0', id: '123' }),
|
fromPartial({ version: '3.1.0', id: '123' }),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByText('Visit stats')).toHaveAttribute('href', `/server/123/domain/foo.com${expectedLink}/visits`);
|
expect(screen.getByText('Visit stats')).toHaveAttribute('href', `/server/123/domain/foo.com${expectedLink}/visits`);
|
||||||
@@ -46,8 +44,8 @@ describe('<DomainDropdown />', () => {
|
|||||||
[false, '2.9.0' as SemVer, true],
|
[false, '2.9.0' as SemVer, true],
|
||||||
])('allows editing certain the domains', (isDefault, serverVersion, canBeEdited) => {
|
])('allows editing certain the domains', (isDefault, serverVersion, canBeEdited) => {
|
||||||
setUp(
|
setUp(
|
||||||
Mock.of<Domain>({ domain: 'foo.com', isDefault }),
|
fromPartial({ domain: 'foo.com', isDefault }),
|
||||||
Mock.of<ReachableServer>({ version: serverVersion, id: '123' }),
|
fromPartial({ version: serverVersion, id: '123' }),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (canBeEdited) {
|
if (canBeEdited) {
|
||||||
@@ -62,7 +60,7 @@ describe('<DomainDropdown />', () => {
|
|||||||
['bar.org'],
|
['bar.org'],
|
||||||
['baz.net'],
|
['baz.net'],
|
||||||
])('displays modal when editing redirects', async (domain) => {
|
])('displays modal when editing redirects', async (domain) => {
|
||||||
const { user } = setUp(Mock.of<Domain>({ domain, isDefault: false }));
|
const { user } = setUp(fromPartial({ domain, isDefault: false }));
|
||||||
|
|
||||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByRole('form')).not.toBeInTheDocument();
|
expect(screen.queryByRole('form')).not.toBeInTheDocument();
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { DomainStatus } from '../../../src/domains/data';
|
import type { DomainStatus } from '../../../src/domains/data';
|
||||||
import { DomainStatusIcon } from '../../../src/domains/helpers/DomainStatusIcon';
|
import { DomainStatusIcon } from '../../../src/domains/helpers/DomainStatusIcon';
|
||||||
import { renderWithEvents } from '../../__helpers__/setUpTest';
|
import { renderWithEvents } from '../../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<DomainStatusIcon />', () => {
|
describe('<DomainStatusIcon />', () => {
|
||||||
const matchMedia = jest.fn().mockReturnValue(Mock.of<MediaQueryList>({ matches: false }));
|
const matchMedia = vi.fn().mockReturnValue(fromPartial<MediaQueryList>({ matches: false }));
|
||||||
const setUp = (status: DomainStatus) => renderWithEvents(
|
const setUp = (status: DomainStatus) => renderWithEvents(
|
||||||
<DomainStatusIcon status={status} matchMedia={matchMedia} />,
|
<DomainStatusIcon status={status} matchMedia={matchMedia} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
beforeEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
['validating' as DomainStatus],
|
['validating' as DomainStatus],
|
||||||
['invalid' as DomainStatus],
|
['invalid' as DomainStatus],
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { ShlinkDomain } from '../../../src/api/types';
|
import type { ShlinkDomain } from '../../../src/api/types';
|
||||||
import { EditDomainRedirectsModal } from '../../../src/domains/helpers/EditDomainRedirectsModal';
|
import { EditDomainRedirectsModal } from '../../../src/domains/helpers/EditDomainRedirectsModal';
|
||||||
import { renderWithEvents } from '../../__helpers__/setUpTest';
|
import { renderWithEvents } from '../../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<EditDomainRedirectsModal />', () => {
|
describe('<EditDomainRedirectsModal />', () => {
|
||||||
const editDomainRedirects = jest.fn().mockResolvedValue(undefined);
|
const editDomainRedirects = vi.fn().mockResolvedValue(undefined);
|
||||||
const toggle = jest.fn();
|
const toggle = vi.fn();
|
||||||
const domain = Mock.of<ShlinkDomain>({
|
const domain = fromPartial<ShlinkDomain>({
|
||||||
domain: 'foo.com',
|
domain: 'foo.com',
|
||||||
redirects: {
|
redirects: {
|
||||||
baseUrlRedirect: 'baz',
|
baseUrlRedirect: 'baz',
|
||||||
@@ -17,8 +17,6 @@ describe('<EditDomainRedirectsModal />', () => {
|
|||||||
<EditDomainRedirectsModal domain={domain} isOpen toggle={toggle} editDomainRedirects={editDomainRedirects} />,
|
<EditDomainRedirectsModal domain={domain} isOpen toggle={toggle} editDomainRedirects={editDomainRedirects} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('renders domain in header', () => {
|
it('renders domain in header', () => {
|
||||||
setUp();
|
setUp();
|
||||||
expect(screen.getByRole('heading')).toHaveTextContent('Edit redirects for foo.com');
|
expect(screen.getByRole('heading')).toHaveTextContent('Edit redirects for foo.com');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`<DomainStatusIcon /> renders expected icon and tooltip when status is not validating 1`] = `
|
exports[`<DomainStatusIcon /> > renders expected icon and tooltip when status is not validating 1`] = `
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="svg-inline--fa fa-circle-notch fa-spin fa-fw "
|
class="svg-inline--fa fa-circle-notch fa-spin fa-fw "
|
||||||
@@ -18,7 +18,7 @@ exports[`<DomainStatusIcon /> renders expected icon and tooltip when status is n
|
|||||||
</svg>
|
</svg>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<DomainStatusIcon /> renders expected icon and tooltip when status is not validating 2`] = `
|
exports[`<DomainStatusIcon /> > renders expected icon and tooltip when status is not validating 2`] = `
|
||||||
<span>
|
<span>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -38,7 +38,7 @@ exports[`<DomainStatusIcon /> renders expected icon and tooltip when status is n
|
|||||||
</span>
|
</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<DomainStatusIcon /> renders expected icon and tooltip when status is not validating 3`] = `
|
exports[`<DomainStatusIcon /> > renders expected icon and tooltip when status is not validating 3`] = `
|
||||||
<span>
|
<span>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -58,7 +58,7 @@ exports[`<DomainStatusIcon /> renders expected icon and tooltip when status is n
|
|||||||
</span>
|
</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<DomainStatusIcon /> renders proper tooltip based on state 1`] = `
|
exports[`<DomainStatusIcon /> > renders proper tooltip based on state 1`] = `
|
||||||
<div
|
<div
|
||||||
class="tooltip-inner"
|
class="tooltip-inner"
|
||||||
role="tooltip"
|
role="tooltip"
|
||||||
@@ -79,7 +79,7 @@ exports[`<DomainStatusIcon /> renders proper tooltip based on state 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<DomainStatusIcon /> renders proper tooltip based on state 2`] = `
|
exports[`<DomainStatusIcon /> > renders proper tooltip based on state 2`] = `
|
||||||
<div
|
<div
|
||||||
class="tooltip-inner"
|
class="tooltip-inner"
|
||||||
role="tooltip"
|
role="tooltip"
|
||||||
|
|||||||
@@ -1,25 +1,22 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient';
|
import type { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient';
|
||||||
import type { ShlinkDomainRedirects } from '../../../src/api/types';
|
import type { ShlinkDomainRedirects } from '../../../src/api/types';
|
||||||
import type { EditDomainRedirects } from '../../../src/domains/reducers/domainRedirects';
|
|
||||||
import { editDomainRedirects } from '../../../src/domains/reducers/domainRedirects';
|
import { editDomainRedirects } from '../../../src/domains/reducers/domainRedirects';
|
||||||
|
|
||||||
describe('domainRedirectsReducer', () => {
|
describe('domainRedirectsReducer', () => {
|
||||||
beforeEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
describe('editDomainRedirects', () => {
|
describe('editDomainRedirects', () => {
|
||||||
const domain = 'example.com';
|
const domain = 'example.com';
|
||||||
const redirects = Mock.all<ShlinkDomainRedirects>();
|
const redirects = fromPartial<ShlinkDomainRedirects>({});
|
||||||
const dispatch = jest.fn();
|
const dispatch = vi.fn();
|
||||||
const getState = jest.fn();
|
const getState = vi.fn();
|
||||||
const editDomainRedirectsCall = jest.fn();
|
const editDomainRedirectsCall = vi.fn();
|
||||||
const buildShlinkApiClient = () => Mock.of<ShlinkApiClient>({ editDomainRedirects: editDomainRedirectsCall });
|
const buildShlinkApiClient = () => fromPartial<ShlinkApiClient>({ editDomainRedirects: editDomainRedirectsCall });
|
||||||
const editDomainRedirectsAction = editDomainRedirects(buildShlinkApiClient);
|
const editDomainRedirectsAction = editDomainRedirects(buildShlinkApiClient);
|
||||||
|
|
||||||
it('dispatches domain and redirects once loaded', async () => {
|
it('dispatches domain and redirects once loaded', async () => {
|
||||||
editDomainRedirectsCall.mockResolvedValue(redirects);
|
editDomainRedirectsCall.mockResolvedValue(redirects);
|
||||||
|
|
||||||
await editDomainRedirectsAction(Mock.of<EditDomainRedirects>({ domain }))(dispatch, getState, {});
|
await editDomainRedirectsAction(fromPartial({ domain }))(dispatch, getState, {});
|
||||||
|
|
||||||
expect(dispatch).toHaveBeenCalledTimes(2);
|
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||||
expect(dispatch).toHaveBeenLastCalledWith(expect.objectContaining({
|
expect(dispatch).toHaveBeenLastCalledWith(expect.objectContaining({
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient';
|
import type { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient';
|
||||||
import type { ShlinkDomainRedirects } from '../../../src/api/types';
|
import type { ShlinkDomainRedirects } from '../../../src/api/types';
|
||||||
import { parseApiError } from '../../../src/api/utils';
|
import { parseApiError } from '../../../src/api/utils';
|
||||||
@@ -6,26 +6,23 @@ import type { ShlinkState } from '../../../src/container/types';
|
|||||||
import type { Domain } from '../../../src/domains/data';
|
import type { Domain } from '../../../src/domains/data';
|
||||||
import type { EditDomainRedirects } from '../../../src/domains/reducers/domainRedirects';
|
import type { EditDomainRedirects } from '../../../src/domains/reducers/domainRedirects';
|
||||||
import { editDomainRedirects } from '../../../src/domains/reducers/domainRedirects';
|
import { editDomainRedirects } from '../../../src/domains/reducers/domainRedirects';
|
||||||
import type {
|
|
||||||
DomainsList } from '../../../src/domains/reducers/domainsList';
|
|
||||||
import {
|
import {
|
||||||
domainsListReducerCreator,
|
domainsListReducerCreator,
|
||||||
replaceRedirectsOnDomain,
|
replaceRedirectsOnDomain,
|
||||||
replaceStatusOnDomain,
|
replaceStatusOnDomain,
|
||||||
} from '../../../src/domains/reducers/domainsList';
|
} from '../../../src/domains/reducers/domainsList';
|
||||||
import type { SelectedServer, ServerData } from '../../../src/servers/data';
|
|
||||||
|
|
||||||
describe('domainsListReducer', () => {
|
describe('domainsListReducer', () => {
|
||||||
const dispatch = jest.fn();
|
const dispatch = vi.fn();
|
||||||
const getState = jest.fn();
|
const getState = vi.fn();
|
||||||
const listDomains = jest.fn();
|
const listDomains = vi.fn();
|
||||||
const health = jest.fn();
|
const health = vi.fn();
|
||||||
const buildShlinkApiClient = () => Mock.of<ShlinkApiClient>({ listDomains, health });
|
const buildShlinkApiClient = () => fromPartial<ShlinkApiClient>({ listDomains, health });
|
||||||
const filteredDomains = [
|
const filteredDomains: Domain[] = [
|
||||||
Mock.of<Domain>({ domain: 'foo', status: 'validating' }),
|
fromPartial({ domain: 'foo', status: 'validating' }),
|
||||||
Mock.of<Domain>({ domain: 'Boo', status: 'validating' }),
|
fromPartial({ domain: 'Boo', status: 'validating' }),
|
||||||
];
|
];
|
||||||
const domains = [...filteredDomains, Mock.of<Domain>({ domain: 'bar', status: 'validating' })];
|
const domains: Domain[] = [...filteredDomains, fromPartial({ domain: 'bar', status: 'validating' })];
|
||||||
const error = { type: 'NOT_FOUND', status: 404 } as unknown as Error;
|
const error = { type: 'NOT_FOUND', status: 404 } as unknown as Error;
|
||||||
const editDomainRedirectsThunk = editDomainRedirects(buildShlinkApiClient);
|
const editDomainRedirectsThunk = editDomainRedirects(buildShlinkApiClient);
|
||||||
const { reducer, listDomains: listDomainsAction, checkDomainHealth, filterDomains } = domainsListReducerCreator(
|
const { reducer, listDomains: listDomainsAction, checkDomainHealth, filterDomains } = domainsListReducerCreator(
|
||||||
@@ -33,8 +30,6 @@ describe('domainsListReducer', () => {
|
|||||||
editDomainRedirectsThunk,
|
editDomainRedirectsThunk,
|
||||||
);
|
);
|
||||||
|
|
||||||
beforeEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
describe('reducer', () => {
|
describe('reducer', () => {
|
||||||
it('returns loading on LIST_DOMAINS_START', () => {
|
it('returns loading on LIST_DOMAINS_START', () => {
|
||||||
expect(reducer(undefined, listDomainsAction.pending(''))).toEqual(
|
expect(reducer(undefined, listDomainsAction.pending(''))).toEqual(
|
||||||
@@ -55,7 +50,7 @@ describe('domainsListReducer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('filters domains on FILTER_DOMAINS', () => {
|
it('filters domains on FILTER_DOMAINS', () => {
|
||||||
expect(reducer(Mock.of<DomainsList>({ domains }), filterDomains('oO'))).toEqual({ domains, filteredDomains });
|
expect(reducer(fromPartial({ domains }), filterDomains('oO'))).toEqual({ domains, filteredDomains });
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
@@ -71,7 +66,7 @@ describe('domainsListReducer', () => {
|
|||||||
const editDomainRedirects: EditDomainRedirects = { domain, redirects };
|
const editDomainRedirects: EditDomainRedirects = { domain, redirects };
|
||||||
|
|
||||||
expect(reducer(
|
expect(reducer(
|
||||||
Mock.of<DomainsList>({ domains, filteredDomains }),
|
fromPartial({ domains, filteredDomains }),
|
||||||
editDomainRedirectsThunk.fulfilled(editDomainRedirects, '', editDomainRedirects),
|
editDomainRedirectsThunk.fulfilled(editDomainRedirects, '', editDomainRedirects),
|
||||||
)).toEqual({
|
)).toEqual({
|
||||||
domains: domains.map(replaceRedirectsOnDomain(editDomainRedirects)),
|
domains: domains.map(replaceRedirectsOnDomain(editDomainRedirects)),
|
||||||
@@ -85,7 +80,7 @@ describe('domainsListReducer', () => {
|
|||||||
['does_not_exist'],
|
['does_not_exist'],
|
||||||
])('replaces status on proper domain on VALIDATE_DOMAIN', (domain) => {
|
])('replaces status on proper domain on VALIDATE_DOMAIN', (domain) => {
|
||||||
expect(reducer(
|
expect(reducer(
|
||||||
Mock.of<DomainsList>({ domains, filteredDomains }),
|
fromPartial({ domains, filteredDomains }),
|
||||||
checkDomainHealth.fulfilled({ domain, status: 'valid' }, '', ''),
|
checkDomainHealth.fulfilled({ domain, status: 'valid' }, '', ''),
|
||||||
)).toEqual({
|
)).toEqual({
|
||||||
domains: domains.map(replaceStatusOnDomain(domain, 'valid')),
|
domains: domains.map(replaceStatusOnDomain(domain, 'valid')),
|
||||||
@@ -122,8 +117,8 @@ describe('domainsListReducer', () => {
|
|||||||
const domain = 'example.com';
|
const domain = 'example.com';
|
||||||
|
|
||||||
it('dispatches invalid status when selected server does not have all required data', async () => {
|
it('dispatches invalid status when selected server does not have all required data', async () => {
|
||||||
getState.mockReturnValue(Mock.of<ShlinkState>({
|
getState.mockReturnValue(fromPartial<ShlinkState>({
|
||||||
selectedServer: Mock.all<SelectedServer>(),
|
selectedServer: {},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await checkDomainHealth(domain)(dispatch, getState, {});
|
await checkDomainHealth(domain)(dispatch, getState, {});
|
||||||
@@ -136,11 +131,11 @@ describe('domainsListReducer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('dispatches invalid status when health endpoint returns an error', async () => {
|
it('dispatches invalid status when health endpoint returns an error', async () => {
|
||||||
getState.mockReturnValue(Mock.of<ShlinkState>({
|
getState.mockReturnValue(fromPartial<ShlinkState>({
|
||||||
selectedServer: Mock.of<ServerData>({
|
selectedServer: {
|
||||||
url: 'https://myerver.com',
|
url: 'https://myerver.com',
|
||||||
apiKey: '123',
|
apiKey: '123',
|
||||||
}),
|
},
|
||||||
}));
|
}));
|
||||||
health.mockRejectedValue({});
|
health.mockRejectedValue({});
|
||||||
|
|
||||||
@@ -160,11 +155,11 @@ describe('domainsListReducer', () => {
|
|||||||
healthStatus,
|
healthStatus,
|
||||||
expectedStatus,
|
expectedStatus,
|
||||||
) => {
|
) => {
|
||||||
getState.mockReturnValue(Mock.of<ShlinkState>({
|
getState.mockReturnValue(fromPartial<ShlinkState>({
|
||||||
selectedServer: Mock.of<ServerData>({
|
selectedServer: {
|
||||||
url: 'https://myerver.com',
|
url: 'https://myerver.com',
|
||||||
apiKey: '123',
|
apiKey: '123',
|
||||||
}),
|
},
|
||||||
}));
|
}));
|
||||||
health.mockResolvedValue({ status: healthStatus });
|
health.mockResolvedValue({ status: healthStatus });
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||||
import { identity } from 'ramda';
|
import { identity } from 'ramda';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import { bindToMercureTopic } from '../../../src/mercure/helpers';
|
import { bindToMercureTopic } from '../../../src/mercure/helpers';
|
||||||
import type { MercureInfo } from '../../../src/mercure/reducers/mercureInfo';
|
import type { MercureInfo } from '../../../src/mercure/reducers/mercureInfo';
|
||||||
|
|
||||||
jest.mock('event-source-polyfill');
|
vi.mock('event-source-polyfill');
|
||||||
|
|
||||||
describe('helpers', () => {
|
describe('helpers', () => {
|
||||||
afterEach(jest.resetAllMocks);
|
|
||||||
|
|
||||||
describe('bindToMercureTopic', () => {
|
describe('bindToMercureTopic', () => {
|
||||||
const onMessage = jest.fn();
|
const onMessage = vi.fn();
|
||||||
const onTokenExpired = jest.fn();
|
const onTokenExpired = vi.fn();
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[Mock.of<MercureInfo>({ loading: true, error: false, mercureHubUrl: 'foo' })],
|
[fromPartial<MercureInfo>({ loading: true, error: false, mercureHubUrl: 'foo' })],
|
||||||
[Mock.of<MercureInfo>({ loading: false, error: true, mercureHubUrl: 'foo' })],
|
[fromPartial<MercureInfo>({ loading: false, error: true, mercureHubUrl: 'foo' })],
|
||||||
[Mock.of<MercureInfo>({ loading: true, error: true, mercureHubUrl: 'foo' })],
|
[fromPartial<MercureInfo>({ loading: true, error: true, mercureHubUrl: 'foo' })],
|
||||||
[Mock.of<MercureInfo>({ loading: false, error: false, mercureHubUrl: undefined })],
|
[fromPartial<MercureInfo>({ loading: false, error: false, mercureHubUrl: undefined })],
|
||||||
[Mock.of<MercureInfo>({ loading: true, error: true, mercureHubUrl: undefined })],
|
[fromPartial<MercureInfo>({ loading: true, error: true, mercureHubUrl: undefined })],
|
||||||
])('does not bind an EventSource when loading, error or no hub URL', (mercureInfo) => {
|
])('does not bind an EventSource when loading, error or no hub URL', (mercureInfo) => {
|
||||||
bindToMercureTopic(mercureInfo, [''], identity, () => {});
|
bindToMercureTopic(mercureInfo, [''], identity, () => {});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient';
|
import type { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient';
|
||||||
import type { GetState } from '../../../src/container/types';
|
import type { GetState } from '../../../src/container/types';
|
||||||
import { mercureInfoReducerCreator } from '../../../src/mercure/reducers/mercureInfo';
|
import { mercureInfoReducerCreator } from '../../../src/mercure/reducers/mercureInfo';
|
||||||
@@ -8,12 +8,10 @@ describe('mercureInfoReducer', () => {
|
|||||||
mercureHubUrl: 'http://example.com/.well-known/mercure',
|
mercureHubUrl: 'http://example.com/.well-known/mercure',
|
||||||
token: 'abc.123.def',
|
token: 'abc.123.def',
|
||||||
};
|
};
|
||||||
const getMercureInfo = jest.fn();
|
const getMercureInfo = vi.fn();
|
||||||
const buildShlinkApiClient = () => Mock.of<ShlinkApiClient>({ mercureInfo: getMercureInfo });
|
const buildShlinkApiClient = () => fromPartial<ShlinkApiClient>({ mercureInfo: getMercureInfo });
|
||||||
const { loadMercureInfo, reducer } = mercureInfoReducerCreator(buildShlinkApiClient);
|
const { loadMercureInfo, reducer } = mercureInfoReducerCreator(buildShlinkApiClient);
|
||||||
|
|
||||||
beforeEach(jest.resetAllMocks);
|
|
||||||
|
|
||||||
describe('reducer', () => {
|
describe('reducer', () => {
|
||||||
it('returns loading on GET_MERCURE_INFO_START', () => {
|
it('returns loading on GET_MERCURE_INFO_START', () => {
|
||||||
expect(reducer(undefined, loadMercureInfo.pending(''))).toEqual({
|
expect(reducer(undefined, loadMercureInfo.pending(''))).toEqual({
|
||||||
@@ -37,8 +35,8 @@ describe('mercureInfoReducer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('loadMercureInfo', () => {
|
describe('loadMercureInfo', () => {
|
||||||
const dispatch = jest.fn();
|
const dispatch = vi.fn();
|
||||||
const createGetStateMock = (enabled: boolean): GetState => jest.fn().mockReturnValue({
|
const createGetStateMock = (enabled: boolean): GetState => vi.fn().mockReturnValue({
|
||||||
settings: {
|
settings: {
|
||||||
realTimeUpdates: { enabled },
|
realTimeUpdates: { enabled },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import { CreateServer as createCreateServer } from '../../src/servers/CreateServer';
|
import { CreateServer as createCreateServer } from '../../src/servers/CreateServer';
|
||||||
import type { ServerWithId } from '../../src/servers/data';
|
import type { ServerWithId } from '../../src/servers/data';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: jest.fn() }));
|
vi.mock('react-router-dom', async () => ({
|
||||||
|
...(await vi.importActual<any>('react-router-dom')),
|
||||||
|
useNavigate: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('<CreateServer />', () => {
|
describe('<CreateServer />', () => {
|
||||||
const createServersMock = jest.fn();
|
const createServersMock = vi.fn();
|
||||||
const navigate = jest.fn();
|
const navigate = vi.fn();
|
||||||
const servers = { foo: Mock.of<ServerWithId>({ url: 'https://existing_url.com', apiKey: 'existing_api_key' }) };
|
const servers = { foo: fromPartial<ServerWithId>({ url: 'https://existing_url.com', apiKey: 'existing_api_key' }) };
|
||||||
const setUp = (serversImported = false, importFailed = false) => {
|
const setUp = (serversImported = false, importFailed = false) => {
|
||||||
(useNavigate as any).mockReturnValue(navigate);
|
(useNavigate as any).mockReturnValue(navigate);
|
||||||
|
|
||||||
let callCount = 0;
|
let callCount = 0;
|
||||||
const useTimeoutToggle = jest.fn().mockImplementation(() => {
|
const useTimeoutToggle = vi.fn().mockImplementation(() => {
|
||||||
const result = [callCount % 2 === 0 ? serversImported : importFailed, () => null];
|
const result = [callCount % 2 === 0 ? serversImported : importFailed, () => null];
|
||||||
callCount += 1;
|
callCount += 1;
|
||||||
return result;
|
return result;
|
||||||
@@ -25,8 +28,6 @@ describe('<CreateServer />', () => {
|
|||||||
return renderWithEvents(<CreateServer createServers={createServersMock} servers={servers} />);
|
return renderWithEvents(<CreateServer createServers={createServersMock} servers={servers} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('shows success message when imported is true', () => {
|
it('shows success message when imported is true', () => {
|
||||||
setUp(true);
|
setUp(true);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { screen, waitFor } from '@testing-library/react';
|
import { screen, waitFor } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import type { ServerWithId } from '../../src/servers/data';
|
|
||||||
import { DeleteServerButton as createDeleteServerButton } from '../../src/servers/DeleteServerButton';
|
import { DeleteServerButton as createDeleteServerButton } from '../../src/servers/DeleteServerButton';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
@@ -10,7 +9,7 @@ describe('<DeleteServerButton />', () => {
|
|||||||
({ isOpen }) => <>DeleteServerModal {isOpen ? '[Open]' : '[Closed]'}</>,
|
({ isOpen }) => <>DeleteServerModal {isOpen ? '[Open]' : '[Closed]'}</>,
|
||||||
);
|
);
|
||||||
const setUp = (children?: ReactNode) => renderWithEvents(
|
const setUp = (children?: ReactNode) => renderWithEvents(
|
||||||
<DeleteServerButton server={Mock.all<ServerWithId>()} textClassName="button">{children}</DeleteServerButton>,
|
<DeleteServerButton server={fromPartial({})} textClassName="button">{children}</DeleteServerButton>,
|
||||||
);
|
);
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import { screen, waitFor } from '@testing-library/react';
|
import { screen, waitFor } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import type { ServerWithId } from '../../src/servers/data';
|
|
||||||
import { DeleteServerModal } from '../../src/servers/DeleteServerModal';
|
import { DeleteServerModal } from '../../src/servers/DeleteServerModal';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
import { TestModalWrapper } from '../__helpers__/TestModalWrapper';
|
import { TestModalWrapper } from '../__helpers__/TestModalWrapper';
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: jest.fn() }));
|
vi.mock('react-router-dom', async () => ({
|
||||||
|
...(await vi.importActual<any>('react-router-dom')),
|
||||||
|
useNavigate: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('<DeleteServerModal />', () => {
|
describe('<DeleteServerModal />', () => {
|
||||||
const deleteServerMock = jest.fn();
|
const deleteServerMock = vi.fn();
|
||||||
const navigate = jest.fn();
|
const navigate = vi.fn();
|
||||||
const serverName = 'the_server_name';
|
const serverName = 'the_server_name';
|
||||||
const setUp = () => {
|
const setUp = () => {
|
||||||
(useNavigate as any).mockReturnValue(navigate);
|
(useNavigate as any).mockReturnValue(navigate);
|
||||||
@@ -20,7 +22,7 @@ describe('<DeleteServerModal />', () => {
|
|||||||
renderModal={(args) => (
|
renderModal={(args) => (
|
||||||
<DeleteServerModal
|
<DeleteServerModal
|
||||||
{...args}
|
{...args}
|
||||||
server={Mock.of<ServerWithId>({ name: serverName })}
|
server={fromPartial({ name: serverName })}
|
||||||
deleteServer={deleteServerMock}
|
deleteServer={deleteServerMock}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -28,8 +30,6 @@ describe('<DeleteServerModal />', () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('renders a modal window', () => {
|
it('renders a modal window', () => {
|
||||||
setUp();
|
setUp();
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import { fireEvent, screen } from '@testing-library/react';
|
import { fireEvent, screen } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { MemoryRouter, useNavigate } from 'react-router-dom';
|
import { MemoryRouter, useNavigate } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import type { ReachableServer, SelectedServer } from '../../src/servers/data';
|
import type { ReachableServer, SelectedServer } from '../../src/servers/data';
|
||||||
import { EditServer as editServerConstruct } from '../../src/servers/EditServer';
|
import { EditServer as editServerConstruct } from '../../src/servers/EditServer';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: jest.fn() }));
|
vi.mock('react-router-dom', async () => ({
|
||||||
|
...(await vi.importActual<any>('react-router-dom')),
|
||||||
|
useNavigate: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('<EditServer />', () => {
|
describe('<EditServer />', () => {
|
||||||
const ServerError = jest.fn();
|
const ServerError = vi.fn();
|
||||||
const editServerMock = jest.fn();
|
const editServerMock = vi.fn();
|
||||||
const navigate = jest.fn();
|
const navigate = vi.fn();
|
||||||
const defaultSelectedServer = Mock.of<ReachableServer>({
|
const defaultSelectedServer = fromPartial<ReachableServer>({
|
||||||
id: 'abc123',
|
id: 'abc123',
|
||||||
name: 'the_name',
|
name: 'the_name',
|
||||||
url: 'the_url',
|
url: 'the_url',
|
||||||
@@ -20,7 +23,7 @@ describe('<EditServer />', () => {
|
|||||||
const EditServer = editServerConstruct(ServerError);
|
const EditServer = editServerConstruct(ServerError);
|
||||||
const setUp = (selectedServer: SelectedServer = defaultSelectedServer) => renderWithEvents(
|
const setUp = (selectedServer: SelectedServer = defaultSelectedServer) => renderWithEvents(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<EditServer editServer={editServerMock} selectedServer={selectedServer} selectServer={jest.fn()} />
|
<EditServer editServer={editServerMock} selectedServer={selectedServer} selectServer={vi.fn()} />
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -28,10 +31,8 @@ describe('<EditServer />', () => {
|
|||||||
(useNavigate as any).mockReturnValue(navigate);
|
(useNavigate as any).mockReturnValue(navigate);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('renders nothing if selected server is not reachable', () => {
|
it('renders nothing if selected server is not reachable', () => {
|
||||||
setUp(Mock.all<SelectedServer>());
|
setUp(fromPartial<SelectedServer>({}));
|
||||||
|
|
||||||
expect(screen.queryByText('Edit')).not.toBeInTheDocument();
|
expect(screen.queryByText('Edit')).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText('Cancel')).not.toBeInTheDocument();
|
expect(screen.queryByText('Cancel')).not.toBeInTheDocument();
|
||||||
|
|||||||
@@ -1,30 +1,28 @@
|
|||||||
import { screen, waitFor } from '@testing-library/react';
|
import { screen, waitFor } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import type { ServersMap, ServerWithId } from '../../src/servers/data';
|
import type { ServersMap, ServerWithId } from '../../src/servers/data';
|
||||||
import { ManageServers as createManageServers } from '../../src/servers/ManageServers';
|
import { ManageServers as createManageServers } from '../../src/servers/ManageServers';
|
||||||
import type { ServersExporter } from '../../src/servers/services/ServersExporter';
|
import type { ServersExporter } from '../../src/servers/services/ServersExporter';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<ManageServers />', () => {
|
describe('<ManageServers />', () => {
|
||||||
const exportServers = jest.fn();
|
const exportServers = vi.fn();
|
||||||
const serversExporter = Mock.of<ServersExporter>({ exportServers });
|
const serversExporter = fromPartial<ServersExporter>({ exportServers });
|
||||||
const useTimeoutToggle = jest.fn().mockReturnValue([false, jest.fn()]);
|
const useTimeoutToggle = vi.fn().mockReturnValue([false, vi.fn()]);
|
||||||
const ManageServers = createManageServers(
|
const ManageServers = createManageServers(
|
||||||
serversExporter,
|
serversExporter,
|
||||||
() => <span>ImportServersBtn</span>,
|
() => <span>ImportServersBtn</span>,
|
||||||
useTimeoutToggle,
|
useTimeoutToggle,
|
||||||
({ hasAutoConnect }) => <tr><td>ManageServersRow {hasAutoConnect ? '[YES]' : '[NO]'}</td></tr>,
|
({ hasAutoConnect }) => <tr><td>ManageServersRow {hasAutoConnect ? '[YES]' : '[NO]'}</td></tr>,
|
||||||
);
|
);
|
||||||
const createServerMock = (value: string, autoConnect = false) => Mock.of<ServerWithId>(
|
const createServerMock = (value: string, autoConnect = false) => fromPartial<ServerWithId>(
|
||||||
{ id: value, name: value, url: value, autoConnect },
|
{ id: value, name: value, url: value, autoConnect },
|
||||||
);
|
);
|
||||||
const setUp = (servers: ServersMap = {}) => renderWithEvents(
|
const setUp = (servers: ServersMap = {}) => renderWithEvents(
|
||||||
<MemoryRouter><ManageServers servers={servers} /></MemoryRouter>,
|
<MemoryRouter><ManageServers servers={servers} /></MemoryRouter>,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('shows search field which allows searching servers, affecting te amount of rendered rows', async () => {
|
it('shows search field which allows searching servers, affecting te amount of rendered rows', async () => {
|
||||||
const { user } = setUp({
|
const { user } = setUp({
|
||||||
foo: createServerMock('foo'),
|
foo: createServerMock('foo'),
|
||||||
@@ -85,7 +83,7 @@ describe('<ManageServers />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([[true], [false]])('shows an error message if an error occurs while importing servers', (hasError) => {
|
it.each([[true], [false]])('shows an error message if an error occurs while importing servers', (hasError) => {
|
||||||
useTimeoutToggle.mockReturnValue([hasError, jest.fn()]);
|
useTimeoutToggle.mockReturnValue([hasError, vi.fn()]);
|
||||||
|
|
||||||
setUp({ foo: createServerMock('foo') });
|
setUp({ foo: createServerMock('foo') });
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import type { ServerWithId } from '../../src/servers/data';
|
import type { ServerWithId } from '../../src/servers/data';
|
||||||
import { ManageServersRowDropdown as createManageServersRowDropdown } from '../../src/servers/ManageServersRowDropdown';
|
import { ManageServersRowDropdown as createManageServersRowDropdown } from '../../src/servers/ManageServersRowDropdown';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
@@ -9,9 +9,9 @@ describe('<ManageServersRowDropdown />', () => {
|
|||||||
const ManageServersRowDropdown = createManageServersRowDropdown(
|
const ManageServersRowDropdown = createManageServersRowDropdown(
|
||||||
({ isOpen }) => <span>DeleteServerModal {isOpen ? '[OPEN]' : '[CLOSED]'}</span>,
|
({ isOpen }) => <span>DeleteServerModal {isOpen ? '[OPEN]' : '[CLOSED]'}</span>,
|
||||||
);
|
);
|
||||||
const setAutoConnect = jest.fn();
|
const setAutoConnect = vi.fn();
|
||||||
const setUp = (autoConnect = false) => {
|
const setUp = (autoConnect = false) => {
|
||||||
const server = Mock.of<ServerWithId>({ id: 'abc123', autoConnect });
|
const server = fromPartial<ServerWithId>({ id: 'abc123', autoConnect });
|
||||||
return renderWithEvents(
|
return renderWithEvents(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<ManageServersRowDropdown setAutoConnect={setAutoConnect} server={server} />
|
<ManageServersRowDropdown setAutoConnect={setAutoConnect} server={server} />
|
||||||
@@ -19,8 +19,6 @@ describe('<ManageServersRowDropdown />', () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('renders expected amount of dropdown items', async () => {
|
it('renders expected amount of dropdown items', async () => {
|
||||||
const { user } = setUp();
|
const { user } = setUp();
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,17 @@
|
|||||||
import { screen, waitFor } from '@testing-library/react';
|
import { screen, waitFor } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import type { MercureInfo } from '../../src/mercure/reducers/mercureInfo';
|
import type { MercureInfo } from '../../src/mercure/reducers/mercureInfo';
|
||||||
import type { ReachableServer } from '../../src/servers/data';
|
|
||||||
import { Overview as overviewCreator } from '../../src/servers/Overview';
|
import { Overview as overviewCreator } from '../../src/servers/Overview';
|
||||||
import type { Settings } from '../../src/settings/reducers/settings';
|
|
||||||
import type { ShortUrlsList as ShortUrlsListState } from '../../src/short-urls/reducers/shortUrlsList';
|
|
||||||
import type { TagsList } from '../../src/tags/reducers/tagsList';
|
|
||||||
import { prettify } from '../../src/utils/helpers/numbers';
|
import { prettify } from '../../src/utils/helpers/numbers';
|
||||||
import type { VisitsOverview } from '../../src/visits/reducers/visitsOverview';
|
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<Overview />', () => {
|
describe('<Overview />', () => {
|
||||||
const ShortUrlsTable = () => <>ShortUrlsTable</>;
|
const ShortUrlsTable = () => <>ShortUrlsTable</>;
|
||||||
const CreateShortUrl = () => <>CreateShortUrl</>;
|
const CreateShortUrl = () => <>CreateShortUrl</>;
|
||||||
const listShortUrls = jest.fn();
|
const listShortUrls = vi.fn();
|
||||||
const listTags = jest.fn();
|
const listTags = vi.fn();
|
||||||
const loadVisitsOverview = jest.fn();
|
const loadVisitsOverview = vi.fn();
|
||||||
const Overview = overviewCreator(ShortUrlsTable, CreateShortUrl);
|
const Overview = overviewCreator(ShortUrlsTable, CreateShortUrl);
|
||||||
const shortUrls = {
|
const shortUrls = {
|
||||||
pagination: { totalItems: 83710 },
|
pagination: { totalItems: 83710 },
|
||||||
@@ -28,18 +23,18 @@ describe('<Overview />', () => {
|
|||||||
listShortUrls={listShortUrls}
|
listShortUrls={listShortUrls}
|
||||||
listTags={listTags}
|
listTags={listTags}
|
||||||
loadVisitsOverview={loadVisitsOverview}
|
loadVisitsOverview={loadVisitsOverview}
|
||||||
shortUrlsList={Mock.of<ShortUrlsListState>({ loading, shortUrls })}
|
shortUrlsList={fromPartial({ loading, shortUrls })}
|
||||||
tagsList={Mock.of<TagsList>({ loading, tags: ['foo', 'bar', 'baz'] })}
|
tagsList={fromPartial({ loading, tags: ['foo', 'bar', 'baz'] })}
|
||||||
visitsOverview={Mock.of<VisitsOverview>({
|
visitsOverview={fromPartial({
|
||||||
loading,
|
loading,
|
||||||
nonOrphanVisits: { total: 3456, bots: 1000, nonBots: 2456 },
|
nonOrphanVisits: { total: 3456, bots: 1000, nonBots: 2456 },
|
||||||
orphanVisits: { total: 28, bots: 15, nonBots: 13 },
|
orphanVisits: { total: 28, bots: 15, nonBots: 13 },
|
||||||
})}
|
})}
|
||||||
selectedServer={Mock.of<ReachableServer>({ id: serverId })}
|
selectedServer={fromPartial({ id: serverId })}
|
||||||
createNewVisits={jest.fn()}
|
createNewVisits={vi.fn()}
|
||||||
loadMercureInfo={jest.fn()}
|
loadMercureInfo={vi.fn()}
|
||||||
mercureInfo={Mock.all<MercureInfo>()}
|
mercureInfo={fromPartial<MercureInfo>({})}
|
||||||
settings={Mock.of<Settings>({ visits: { excludeBots } })}
|
settings={fromPartial({ visits: { excludeBots } })}
|
||||||
/>
|
/>
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { values } from 'ramda';
|
import { values } from 'ramda';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
import type { ServersMap } from '../../src/servers/data';
|
||||||
import type { ServersMap, ServerWithId } from '../../src/servers/data';
|
|
||||||
import { ServersDropdown } from '../../src/servers/ServersDropdown';
|
import { ServersDropdown } from '../../src/servers/ServersDropdown';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<ServersDropdown />', () => {
|
describe('<ServersDropdown />', () => {
|
||||||
const fallbackServers: ServersMap = {
|
const fallbackServers: ServersMap = {
|
||||||
'1a': Mock.of<ServerWithId>({ name: 'foo', id: '1a' }),
|
'1a': fromPartial({ name: 'foo', id: '1a' }),
|
||||||
'2b': Mock.of<ServerWithId>({ name: 'bar', id: '2b' }),
|
'2b': fromPartial({ name: 'bar', id: '2b' }),
|
||||||
'3c': Mock.of<ServerWithId>({ name: 'baz', id: '3c' }),
|
'3c': fromPartial({ name: 'baz', id: '3c' }),
|
||||||
};
|
};
|
||||||
const setUp = (servers: ServersMap = fallbackServers) => renderWithEvents(
|
const setUp = (servers: ServersMap = fallbackServers) => renderWithEvents(
|
||||||
<MemoryRouter><ServersDropdown servers={servers} selectedServer={null} /></MemoryRouter>,
|
<MemoryRouter><ServersDropdown servers={servers} selectedServer={null} /></MemoryRouter>,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import type { ServerWithId } from '../../src/servers/data';
|
import type { ServerWithId } from '../../src/servers/data';
|
||||||
import { ServersListGroup } from '../../src/servers/ServersListGroup';
|
import { ServersListGroup } from '../../src/servers/ServersListGroup';
|
||||||
|
|
||||||
describe('<ServersListGroup />', () => {
|
describe('<ServersListGroup />', () => {
|
||||||
const servers = [
|
const servers: ServerWithId[] = [
|
||||||
Mock.of<ServerWithId>({ name: 'foo', id: '123' }),
|
fromPartial({ name: 'foo', id: '123' }),
|
||||||
Mock.of<ServerWithId>({ name: 'bar', id: '456' }),
|
fromPartial({ name: 'bar', id: '456' }),
|
||||||
];
|
];
|
||||||
const setUp = (params: { servers?: ServerWithId[]; withChildren?: boolean; embedded?: boolean }) => {
|
const setUp = (params: { servers?: ServerWithId[]; withChildren?: boolean; embedded?: boolean }) => {
|
||||||
const { servers = [], withChildren = true, embedded } = params;
|
const { servers = [], withChildren = true, embedded } = params;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`<DeleteServerButton /> renders expected content 1`] = `
|
exports[`<DeleteServerButton /> > renders expected content 1`] = `
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="button"
|
class="button"
|
||||||
@@ -10,7 +10,7 @@ exports[`<DeleteServerButton /> renders expected content 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<DeleteServerButton /> renders expected content 2`] = `
|
exports[`<DeleteServerButton /> > renders expected content 2`] = `
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="button"
|
class="button"
|
||||||
@@ -20,7 +20,7 @@ exports[`<DeleteServerButton /> renders expected content 2`] = `
|
|||||||
</span>
|
</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<DeleteServerButton /> renders expected content 3`] = `
|
exports[`<DeleteServerButton /> > renders expected content 3`] = `
|
||||||
<span>
|
<span>
|
||||||
<span
|
<span
|
||||||
class="button"
|
class="button"
|
||||||
@@ -30,7 +30,7 @@ exports[`<DeleteServerButton /> renders expected content 3`] = `
|
|||||||
</span>
|
</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<DeleteServerButton /> renders expected content 4`] = `
|
exports[`<DeleteServerButton /> > renders expected content 4`] = `
|
||||||
<span>
|
<span>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`<ManageServersRow /> renders auto-connect icon only if server is autoConnect 1`] = `
|
exports[`<ManageServersRow /> > renders auto-connect icon only if server is autoConnect 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -57,7 +57,7 @@ exports[`<ManageServersRow /> renders auto-connect icon only if server is autoCo
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<ManageServersRow /> renders auto-connect icon only if server is autoConnect 2`] = `
|
exports[`<ManageServersRow /> > renders auto-connect icon only if server is autoConnect 2`] = `
|
||||||
<div>
|
<div>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { ServerData } from '../../../src/servers/data';
|
import type { ServerData } from '../../../src/servers/data';
|
||||||
import { DuplicatedServersModal } from '../../../src/servers/helpers/DuplicatedServersModal';
|
import { DuplicatedServersModal } from '../../../src/servers/helpers/DuplicatedServersModal';
|
||||||
import { renderWithEvents } from '../../__helpers__/setUpTest';
|
import { renderWithEvents } from '../../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<DuplicatedServersModal />', () => {
|
describe('<DuplicatedServersModal />', () => {
|
||||||
const onDiscard = jest.fn();
|
const onDiscard = vi.fn();
|
||||||
const onSave = jest.fn();
|
const onSave = vi.fn();
|
||||||
const setUp = (duplicatedServers: ServerData[] = []) => renderWithEvents(
|
const setUp = (duplicatedServers: ServerData[] = []) => renderWithEvents(
|
||||||
<DuplicatedServersModal isOpen duplicatedServers={duplicatedServers} onDiscard={onDiscard} onSave={onSave} />,
|
<DuplicatedServersModal isOpen duplicatedServers={duplicatedServers} onDiscard={onDiscard} onSave={onSave} />,
|
||||||
);
|
);
|
||||||
|
const mockServer = (data: Partial<ServerData> = {}) => fromPartial<ServerData>(data);
|
||||||
beforeEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[[], 0],
|
[[], 0],
|
||||||
[[Mock.all<ServerData>()], 2],
|
[[mockServer()], 2],
|
||||||
[[Mock.all<ServerData>(), Mock.all<ServerData>()], 2],
|
[[mockServer(), mockServer()], 2],
|
||||||
[[Mock.all<ServerData>(), Mock.all<ServerData>(), Mock.all<ServerData>()], 3],
|
[[mockServer(), mockServer(), mockServer()], 3],
|
||||||
[[Mock.all<ServerData>(), Mock.all<ServerData>(), Mock.all<ServerData>(), Mock.all<ServerData>()], 4],
|
[[mockServer(), mockServer(), mockServer(), mockServer()], 4],
|
||||||
])('renders expected amount of items', (duplicatedServers, expectedItems) => {
|
])('renders expected amount of items', (duplicatedServers, expectedItems) => {
|
||||||
setUp(duplicatedServers);
|
setUp(duplicatedServers);
|
||||||
expect(screen.queryAllByRole('listitem')).toHaveLength(expectedItems);
|
expect(screen.queryAllByRole('listitem')).toHaveLength(expectedItems);
|
||||||
@@ -26,7 +25,7 @@ describe('<DuplicatedServersModal />', () => {
|
|||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[
|
[
|
||||||
[Mock.all<ServerData>()],
|
[mockServer()],
|
||||||
{
|
{
|
||||||
header: 'Duplicated server',
|
header: 'Duplicated server',
|
||||||
firstParagraph: 'There is already a server with:',
|
firstParagraph: 'There is already a server with:',
|
||||||
@@ -35,7 +34,7 @@ describe('<DuplicatedServersModal />', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
[Mock.all<ServerData>(), Mock.all<ServerData>()],
|
[mockServer(), mockServer()],
|
||||||
{
|
{
|
||||||
header: 'Duplicated servers',
|
header: 'Duplicated servers',
|
||||||
firstParagraph: 'The next servers already exist:',
|
firstParagraph: 'The next servers already exist:',
|
||||||
@@ -54,10 +53,10 @@ describe('<DuplicatedServersModal />', () => {
|
|||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[[]],
|
[[]],
|
||||||
[[Mock.of<ServerData>({ url: 'url', apiKey: 'apiKey' })]],
|
[[mockServer({ url: 'url', apiKey: 'apiKey' })]],
|
||||||
[[
|
[[
|
||||||
Mock.of<ServerData>({ url: 'url_1', apiKey: 'apiKey_1' }),
|
mockServer({ url: 'url_1', apiKey: 'apiKey_1' }),
|
||||||
Mock.of<ServerData>({ url: 'url_2', apiKey: 'apiKey_2' }),
|
mockServer({ url: 'url_2', apiKey: 'apiKey_2' }),
|
||||||
]],
|
]],
|
||||||
])('displays provided server data', (duplicatedServers) => {
|
])('displays provided server data', (duplicatedServers) => {
|
||||||
setUp(duplicatedServers);
|
setUp(duplicatedServers);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { ServersMap, ServerWithId } from '../../../src/servers/data';
|
import type { ServersMap, ServerWithId } from '../../../src/servers/data';
|
||||||
import type {
|
import type {
|
||||||
ImportServersBtnProps } from '../../../src/servers/helpers/ImportServersBtn';
|
ImportServersBtnProps } from '../../../src/servers/helpers/ImportServersBtn';
|
||||||
@@ -10,10 +10,10 @@ import type { ServersImporter } from '../../../src/servers/services/ServersImpor
|
|||||||
import { renderWithEvents } from '../../__helpers__/setUpTest';
|
import { renderWithEvents } from '../../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<ImportServersBtn />', () => {
|
describe('<ImportServersBtn />', () => {
|
||||||
const onImportMock = jest.fn();
|
const onImportMock = vi.fn();
|
||||||
const createServersMock = jest.fn();
|
const createServersMock = vi.fn();
|
||||||
const importServersFromFile = jest.fn().mockResolvedValue([]);
|
const importServersFromFile = vi.fn().mockResolvedValue([]);
|
||||||
const serversImporterMock = Mock.of<ServersImporter>({ importServersFromFile });
|
const serversImporterMock = fromPartial<ServersImporter>({ importServersFromFile });
|
||||||
const ImportServersBtn = createImportServersBtn(serversImporterMock);
|
const ImportServersBtn = createImportServersBtn(serversImporterMock);
|
||||||
const setUp = (props: Partial<ImportServersBtnProps> = {}, servers: ServersMap = {}) => renderWithEvents(
|
const setUp = (props: Partial<ImportServersBtnProps> = {}, servers: ServersMap = {}) => renderWithEvents(
|
||||||
<ImportServersBtn
|
<ImportServersBtn
|
||||||
@@ -24,8 +24,6 @@ describe('<ImportServersBtn />', () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('shows tooltip on button hover', async () => {
|
it('shows tooltip on button hover', async () => {
|
||||||
const { user } = setUp();
|
const { user } = setUp();
|
||||||
|
|
||||||
@@ -67,8 +65,8 @@ describe('<ImportServersBtn />', () => {
|
|||||||
['Save anyway', true],
|
['Save anyway', true],
|
||||||
['Discard', false],
|
['Discard', false],
|
||||||
])('creates expected servers depending on selected option in modal', async (btnName, savesDuplicatedServers) => {
|
])('creates expected servers depending on selected option in modal', async (btnName, savesDuplicatedServers) => {
|
||||||
const existingServer = Mock.of<ServerWithId>({ id: 'abc', url: 'existingUrl', apiKey: 'existingApiKey' });
|
const existingServer = fromPartial<ServerWithId>({ id: 'abc', url: 'existingUrl', apiKey: 'existingApiKey' });
|
||||||
const newServer = Mock.of<ServerWithId>({ url: 'newUrl', apiKey: 'newApiKey' });
|
const newServer = fromPartial<ServerWithId>({ url: 'newUrl', apiKey: 'newApiKey' });
|
||||||
const { container, user } = setUp({}, { abc: existingServer });
|
const { container, user } = setUp({}, { abc: existingServer });
|
||||||
const input = container.querySelector('[type=file]');
|
const input = container.querySelector('[type=file]');
|
||||||
importServersFromFile.mockResolvedValue([existingServer, newServer]);
|
importServersFromFile.mockResolvedValue([existingServer, newServer]);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import type { NonReachableServer, NotFoundServer } from '../../../src/servers/data';
|
import type { NonReachableServer, NotFoundServer } from '../../../src/servers/data';
|
||||||
import { ServerError as createServerError } from '../../../src/servers/helpers/ServerError';
|
import { ServerError as createServerError } from '../../../src/servers/helpers/ServerError';
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ describe('<ServerError />', () => {
|
|||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[
|
[
|
||||||
Mock.all<NotFoundServer>(),
|
fromPartial<NotFoundServer>({}),
|
||||||
{
|
{
|
||||||
found: ['Could not find this Shlink server.'],
|
found: ['Could not find this Shlink server.'],
|
||||||
notFound: [
|
notFound: [
|
||||||
@@ -20,7 +20,7 @@ describe('<ServerError />', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
Mock.of<NonReachableServer>({ id: 'abc123' }),
|
fromPartial<NonReachableServer>({ id: 'abc123' }),
|
||||||
{
|
{
|
||||||
found: [
|
found: [
|
||||||
'Oops! Could not connect to this Shlink server.',
|
'Oops! Could not connect to this Shlink server.',
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ import { fireEvent, render, screen } from '@testing-library/react';
|
|||||||
import { ServerForm } from '../../../src/servers/helpers/ServerForm';
|
import { ServerForm } from '../../../src/servers/helpers/ServerForm';
|
||||||
|
|
||||||
describe('<ServerForm />', () => {
|
describe('<ServerForm />', () => {
|
||||||
const onSubmit = jest.fn();
|
const onSubmit = vi.fn();
|
||||||
const setUp = () => render(<ServerForm onSubmit={onSubmit}>Something</ServerForm>);
|
const setUp = () => render(<ServerForm onSubmit={onSubmit}>Something</ServerForm>);
|
||||||
|
|
||||||
afterEach(jest.resetAllMocks);
|
|
||||||
|
|
||||||
it('renders components', () => {
|
it('renders components', () => {
|
||||||
setUp();
|
setUp();
|
||||||
|
|
||||||
@@ -18,7 +16,7 @@ describe('<ServerForm />', () => {
|
|||||||
setUp();
|
setUp();
|
||||||
|
|
||||||
expect(onSubmit).not.toHaveBeenCalled();
|
expect(onSubmit).not.toHaveBeenCalled();
|
||||||
fireEvent.submit(screen.getByRole('form'), { preventDefault: jest.fn() });
|
fireEvent.submit(screen.getByRole('form'), { preventDefault: vi.fn() });
|
||||||
expect(onSubmit).toHaveBeenCalled();
|
expect(onSubmit).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { HttpClient } from '../../../src/common/services/HttpClient';
|
import type { HttpClient } from '../../../src/common/services/HttpClient';
|
||||||
import { fetchServers } from '../../../src/servers/reducers/remoteServers';
|
import { fetchServers } from '../../../src/servers/reducers/remoteServers';
|
||||||
|
|
||||||
describe('remoteServersReducer', () => {
|
describe('remoteServersReducer', () => {
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
describe('fetchServers', () => {
|
describe('fetchServers', () => {
|
||||||
const dispatch = jest.fn();
|
const dispatch = vi.fn();
|
||||||
const fetchJson = jest.fn();
|
const fetchJson = vi.fn();
|
||||||
const httpClient = Mock.of<HttpClient>({ fetchJson });
|
const httpClient = fromPartial<HttpClient>({ fetchJson });
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[
|
[
|
||||||
@@ -81,7 +79,7 @@ describe('remoteServersReducer', () => {
|
|||||||
fetchJson.mockResolvedValue(mockedValue);
|
fetchJson.mockResolvedValue(mockedValue);
|
||||||
const doFetchServers = fetchServers(httpClient);
|
const doFetchServers = fetchServers(httpClient);
|
||||||
|
|
||||||
await doFetchServers()(dispatch, jest.fn(), {});
|
await doFetchServers()(dispatch, vi.fn(), {});
|
||||||
|
|
||||||
expect(dispatch).toHaveBeenCalledTimes(3);
|
expect(dispatch).toHaveBeenCalledTimes(3);
|
||||||
expect(dispatch).toHaveBeenNthCalledWith(2, expect.objectContaining({ payload: expectedNewServers }));
|
expect(dispatch).toHaveBeenNthCalledWith(2, expect.objectContaining({ payload: expectedNewServers }));
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import type { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient';
|
import type { ShlinkApiClient } from '../../../src/api/services/ShlinkApiClient';
|
||||||
import type { ShlinkState } from '../../../src/container/types';
|
import type { ShlinkState } from '../../../src/container/types';
|
||||||
@@ -13,28 +13,25 @@ import {
|
|||||||
} from '../../../src/servers/reducers/selectedServer';
|
} from '../../../src/servers/reducers/selectedServer';
|
||||||
|
|
||||||
describe('selectedServerReducer', () => {
|
describe('selectedServerReducer', () => {
|
||||||
const dispatch = jest.fn();
|
const dispatch = vi.fn();
|
||||||
const health = jest.fn();
|
const health = vi.fn();
|
||||||
const buildApiClient = jest.fn().mockReturnValue(Mock.of<ShlinkApiClient>({ health }));
|
const buildApiClient = vi.fn().mockReturnValue(fromPartial<ShlinkApiClient>({ health }));
|
||||||
const selectServer = selectServerCreator(buildApiClient);
|
const selectServer = selectServerCreator(buildApiClient);
|
||||||
const { reducer } = selectedServerReducerCreator(selectServer);
|
const { reducer } = selectedServerReducerCreator(selectServer);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
describe('reducer', () => {
|
describe('reducer', () => {
|
||||||
it('returns default when action is RESET_SELECTED_SERVER', () =>
|
it('returns default when action is RESET_SELECTED_SERVER', () =>
|
||||||
expect(reducer(null, resetSelectedServer())).toBeNull());
|
expect(reducer(null, resetSelectedServer())).toBeNull());
|
||||||
|
|
||||||
it('returns selected server when action is SELECT_SERVER', () => {
|
it('returns selected server when action is SELECT_SERVER', () => {
|
||||||
const payload = Mock.of<RegularServer>({ id: 'abc123' });
|
const payload = fromPartial<RegularServer>({ id: 'abc123' });
|
||||||
|
|
||||||
expect(reducer(null, selectServer.fulfilled(payload, '', ''))).toEqual(payload);
|
expect(reducer(null, selectServer.fulfilled(payload, '', ''))).toEqual(payload);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('selectServer', () => {
|
describe('selectServer', () => {
|
||||||
const version = '1.19.0';
|
const version = '1.19.0';
|
||||||
const createGetStateMock = (id: string) => jest.fn().mockReturnValue({
|
const createGetStateMock = (id: string) => vi.fn().mockReturnValue({
|
||||||
servers: {
|
servers: {
|
||||||
[id]: { id },
|
[id]: { id },
|
||||||
},
|
},
|
||||||
@@ -66,7 +63,7 @@ describe('selectedServerReducer', () => {
|
|||||||
it('dispatches error when health endpoint fails', async () => {
|
it('dispatches error when health endpoint fails', async () => {
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
const getState = createGetStateMock(id);
|
const getState = createGetStateMock(id);
|
||||||
const expectedSelectedServer = Mock.of<NonReachableServer>({ id, serverNotReachable: true });
|
const expectedSelectedServer = fromPartial<NonReachableServer>({ id, serverNotReachable: true });
|
||||||
|
|
||||||
health.mockRejectedValue({});
|
health.mockRejectedValue({});
|
||||||
|
|
||||||
@@ -78,7 +75,7 @@ describe('selectedServerReducer', () => {
|
|||||||
|
|
||||||
it('dispatches error when server is not found', async () => {
|
it('dispatches error when server is not found', async () => {
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
const getState = jest.fn(() => Mock.of<ShlinkState>({ servers: {} }));
|
const getState = vi.fn(() => fromPartial<ShlinkState>({ servers: {} }));
|
||||||
const expectedSelectedServer: NotFoundServer = { serverNotFound: true };
|
const expectedSelectedServer: NotFoundServer = { serverNotFound: true };
|
||||||
|
|
||||||
await selectServer(id)(dispatch, getState, {});
|
await selectServer(id)(dispatch, getState, {});
|
||||||
@@ -90,16 +87,16 @@ describe('selectedServerReducer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('selectServerListener', () => {
|
describe('selectServerListener', () => {
|
||||||
const getState = jest.fn(() => ({}));
|
const getState = vi.fn(() => ({}));
|
||||||
const loadMercureInfo = jest.fn();
|
const loadMercureInfo = vi.fn();
|
||||||
const { middleware } = selectServerListener(selectServer, loadMercureInfo);
|
const { middleware } = selectServerListener(selectServer, loadMercureInfo);
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[Mock.of<ReachableServer>({ version: '1.2.3' }), 1],
|
[fromPartial<ReachableServer>({ version: '1.2.3' }), 1],
|
||||||
[Mock.of<NotFoundServer>({ serverNotFound: true }), 0],
|
[fromPartial<NotFoundServer>({ serverNotFound: true }), 0],
|
||||||
[Mock.of<NonReachableServer>({ serverNotReachable: true }), 0],
|
[fromPartial<NonReachableServer>({ serverNotReachable: true }), 0],
|
||||||
])('dispatches loadMercureInfo when provided server is reachable', (payload, expectedCalls) => {
|
])('dispatches loadMercureInfo when provided server is reachable', (payload, expectedCalls) => {
|
||||||
middleware({ dispatch, getState })(jest.fn())({
|
middleware({ dispatch, getState })(vi.fn())({
|
||||||
payload,
|
payload,
|
||||||
type: selectServer.fulfilled.toString(),
|
type: selectServer.fulfilled.toString(),
|
||||||
});
|
});
|
||||||
@@ -109,8 +106,8 @@ describe('selectedServerReducer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not dispatch loadMercureInfo when action is not of the proper type', () => {
|
it('does not dispatch loadMercureInfo when action is not of the proper type', () => {
|
||||||
middleware({ dispatch, getState })(jest.fn())({
|
middleware({ dispatch, getState })(vi.fn())({
|
||||||
payload: Mock.of<ReachableServer>({ version: '1.2.3' }),
|
payload: fromPartial<ReachableServer>({ version: '1.2.3' }),
|
||||||
type: 'something_else',
|
type: 'something_else',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { dissoc, values } from 'ramda';
|
import { dissoc, values } from 'ramda';
|
||||||
import { Mock } from 'ts-mockery';
|
import type { RegularServer, ServersMap, ServerWithId } from '../../../src/servers/data';
|
||||||
import type { RegularServer, ServerWithId } from '../../../src/servers/data';
|
|
||||||
import {
|
import {
|
||||||
createServers,
|
createServers,
|
||||||
deleteServer,
|
deleteServer,
|
||||||
@@ -10,13 +10,11 @@ import {
|
|||||||
} from '../../../src/servers/reducers/servers';
|
} from '../../../src/servers/reducers/servers';
|
||||||
|
|
||||||
describe('serversReducer', () => {
|
describe('serversReducer', () => {
|
||||||
const list = {
|
const list: ServersMap = {
|
||||||
abc123: Mock.of<RegularServer>({ id: 'abc123' }),
|
abc123: fromPartial({ id: 'abc123' }),
|
||||||
def456: Mock.of<RegularServer>({ id: 'def456' }),
|
def456: fromPartial({ id: 'def456' }),
|
||||||
};
|
};
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
describe('reducer', () => {
|
describe('reducer', () => {
|
||||||
it('returns edited server when action is EDIT_SERVER', () =>
|
it('returns edited server when action is EDIT_SERVER', () =>
|
||||||
expect(serversReducer(list, editServer('abc123', { name: 'foo' }))).toEqual({
|
expect(serversReducer(list, editServer('abc123', { name: 'foo' }))).toEqual({
|
||||||
@@ -31,12 +29,12 @@ describe('serversReducer', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('removes server when action is DELETE_SERVER', () =>
|
it('removes server when action is DELETE_SERVER', () =>
|
||||||
expect(serversReducer(list, deleteServer(Mock.of<ServerWithId>({ id: 'abc123' })))).toEqual({
|
expect(serversReducer(list, deleteServer(fromPartial<ServerWithId>({ id: 'abc123' })))).toEqual({
|
||||||
def456: { id: 'def456' },
|
def456: { id: 'def456' },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('appends server when action is CREATE_SERVERS', () =>
|
it('appends server when action is CREATE_SERVERS', () =>
|
||||||
expect(serversReducer(list, createServers([Mock.of<ServerWithId>({ id: 'ghi789' })]))).toEqual({
|
expect(serversReducer(list, createServers([fromPartial<ServerWithId>({ id: 'ghi789' })]))).toEqual({
|
||||||
abc123: { id: 'abc123' },
|
abc123: { id: 'abc123' },
|
||||||
def456: { id: 'def456' },
|
def456: { id: 'def456' },
|
||||||
ghi789: { id: 'ghi789' },
|
ghi789: { id: 'ghi789' },
|
||||||
@@ -46,7 +44,7 @@ describe('serversReducer', () => {
|
|||||||
[true],
|
[true],
|
||||||
[false],
|
[false],
|
||||||
])('returns state as it is when trying to set auto-connect on invalid server', (autoConnect) =>
|
])('returns state as it is when trying to set auto-connect on invalid server', (autoConnect) =>
|
||||||
expect(serversReducer(list, setAutoConnect(Mock.of<ServerWithId>({ id: 'invalid' }), autoConnect))).toEqual({
|
expect(serversReducer(list, setAutoConnect(fromPartial<ServerWithId>({ id: 'invalid' }), autoConnect))).toEqual({
|
||||||
abc123: { id: 'abc123' },
|
abc123: { id: 'abc123' },
|
||||||
def456: { id: 'def456' },
|
def456: { id: 'def456' },
|
||||||
}));
|
}));
|
||||||
@@ -59,7 +57,7 @@ describe('serversReducer', () => {
|
|||||||
|
|
||||||
expect(serversReducer(
|
expect(serversReducer(
|
||||||
listWithDisabledAutoConnect,
|
listWithDisabledAutoConnect,
|
||||||
setAutoConnect(Mock.of<ServerWithId>({ id: 'abc123' }), false),
|
setAutoConnect(fromPartial<ServerWithId>({ id: 'abc123' }), false),
|
||||||
)).toEqual({
|
)).toEqual({
|
||||||
abc123: { id: 'abc123', autoConnect: false },
|
abc123: { id: 'abc123', autoConnect: false },
|
||||||
def456: { id: 'def456' },
|
def456: { id: 'def456' },
|
||||||
@@ -74,7 +72,7 @@ describe('serversReducer', () => {
|
|||||||
|
|
||||||
expect(serversReducer(
|
expect(serversReducer(
|
||||||
listWithEnabledAutoConnect,
|
listWithEnabledAutoConnect,
|
||||||
setAutoConnect(Mock.of<ServerWithId>({ id: 'def456' }), true),
|
setAutoConnect(fromPartial<ServerWithId>({ id: 'def456' }), true),
|
||||||
)).toEqual({
|
)).toEqual({
|
||||||
abc123: { id: 'abc123', autoConnect: false },
|
abc123: { id: 'abc123', autoConnect: false },
|
||||||
def456: { id: 'def456', autoConnect: true },
|
def456: { id: 'def456', autoConnect: true },
|
||||||
@@ -94,7 +92,7 @@ describe('serversReducer', () => {
|
|||||||
|
|
||||||
describe('deleteServer', () => {
|
describe('deleteServer', () => {
|
||||||
it('returns expected action', () => {
|
it('returns expected action', () => {
|
||||||
const serverToDelete = Mock.of<RegularServer>({ id: 'abc123' });
|
const serverToDelete = fromPartial<RegularServer>({ id: 'abc123' });
|
||||||
const { payload } = deleteServer(serverToDelete);
|
const { payload } = deleteServer(serverToDelete);
|
||||||
|
|
||||||
expect(payload).toEqual({ id: 'abc123' });
|
expect(payload).toEqual({ id: 'abc123' });
|
||||||
@@ -122,7 +120,7 @@ describe('serversReducer', () => {
|
|||||||
[true],
|
[true],
|
||||||
[false],
|
[false],
|
||||||
])('returns expected action', (autoConnect) => {
|
])('returns expected action', (autoConnect) => {
|
||||||
const serverToEdit = Mock.of<RegularServer>({ id: 'abc123' });
|
const serverToEdit = fromPartial<RegularServer>({ id: 'abc123' });
|
||||||
const { payload } = setAutoConnect(serverToEdit, autoConnect);
|
const { payload } = setAutoConnect(serverToEdit, autoConnect);
|
||||||
|
|
||||||
expect(payload).toEqual({ serverId: 'abc123', autoConnect });
|
expect(payload).toEqual({ serverId: 'abc123', autoConnect });
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { ServersExporter } from '../../../src/servers/services/ServersExporter';
|
import { ServersExporter } from '../../../src/servers/services/ServersExporter';
|
||||||
import type { LocalStorage } from '../../../src/utils/services/LocalStorage';
|
import type { LocalStorage } from '../../../src/utils/services/LocalStorage';
|
||||||
import { appendChild, removeChild, windowMock } from '../../__mocks__/Window.mock';
|
import { appendChild, removeChild, windowMock } from '../../__mocks__/Window.mock';
|
||||||
|
|
||||||
describe('ServersExporter', () => {
|
describe('ServersExporter', () => {
|
||||||
const storageMock = Mock.of<LocalStorage>({
|
const storageMock = fromPartial<LocalStorage>({
|
||||||
get: jest.fn(() => ({
|
get: vi.fn(() => ({
|
||||||
abc123: {
|
abc123: {
|
||||||
id: 'abc123',
|
id: 'abc123',
|
||||||
name: 'foo',
|
name: 'foo',
|
||||||
@@ -16,22 +16,20 @@ describe('ServersExporter', () => {
|
|||||||
name: 'bar',
|
name: 'bar',
|
||||||
autoConnect: false,
|
autoConnect: false,
|
||||||
},
|
},
|
||||||
})),
|
} as any)),
|
||||||
});
|
});
|
||||||
const erroneousToCsv = jest.fn(() => {
|
const erroneousToCsv = vi.fn(() => {
|
||||||
throw new Error('');
|
throw new Error('');
|
||||||
});
|
});
|
||||||
const createCsvjsonMock = (throwError = false) => (throwError ? erroneousToCsv : jest.fn(() => ''));
|
const createCsvjsonMock = (throwError = false) => (throwError ? erroneousToCsv : vi.fn(() => ''));
|
||||||
|
|
||||||
beforeEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
describe('exportServers', () => {
|
describe('exportServers', () => {
|
||||||
let originalConsole: Console;
|
let originalConsole: Console;
|
||||||
const error = jest.fn();
|
const error = vi.fn();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
originalConsole = global.console;
|
originalConsole = global.console;
|
||||||
global.console = Mock.of<Console>({ error });
|
global.console = fromPartial<Console>({ error });
|
||||||
(global as any).Blob = class Blob {};
|
(global as any).Blob = class Blob {};
|
||||||
(global as any).URL = { createObjectURL: () => '' };
|
(global as any).URL = { createObjectURL: () => '' };
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { RegularServer } from '../../../src/servers/data';
|
import type { RegularServer } from '../../../src/servers/data';
|
||||||
import { ServersImporter } from '../../../src/servers/services/ServersImporter';
|
import { ServersImporter } from '../../../src/servers/services/ServersImporter';
|
||||||
|
|
||||||
describe('ServersImporter', () => {
|
describe('ServersImporter', () => {
|
||||||
const servers: RegularServer[] = [Mock.all<RegularServer>(), Mock.all<RegularServer>()];
|
const servers: RegularServer[] = [fromPartial<RegularServer>({}), fromPartial<RegularServer>({})];
|
||||||
const csvjsonMock = jest.fn().mockResolvedValue(servers);
|
const csvjsonMock = vi.fn().mockResolvedValue(servers);
|
||||||
const readAsText = jest.fn();
|
const readAsText = vi.fn();
|
||||||
const fileReaderMock = Mock.of<FileReader>({
|
const fileReaderMock = fromPartial<FileReader>({
|
||||||
readAsText,
|
readAsText,
|
||||||
addEventListener: (_eventName: string, listener: (e: ProgressEvent<FileReader>) => void) => listener(
|
addEventListener: ((_eventName: string, listener: (e: ProgressEvent<FileReader>) => void) => listener(
|
||||||
Mock.of<ProgressEvent<FileReader>>({ target: { result: '' } }),
|
fromPartial({ target: { result: '' } }),
|
||||||
),
|
)) as any,
|
||||||
});
|
});
|
||||||
const importer = new ServersImporter(csvjsonMock, () => fileReaderMock);
|
const importer = new ServersImporter(csvjsonMock, () => fileReaderMock);
|
||||||
|
|
||||||
beforeEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
describe('importServersFromFile', () => {
|
describe('importServersFromFile', () => {
|
||||||
it('rejects with error if no file was provided', async () => {
|
it('rejects with error if no file was provided', async () => {
|
||||||
await expect(importer.importServersFromFile()).rejects.toEqual(
|
await expect(importer.importServersFromFile()).rejects.toEqual(
|
||||||
@@ -28,7 +26,7 @@ describe('ServersImporter', () => {
|
|||||||
|
|
||||||
csvjsonMock.mockRejectedValue(expectedError);
|
csvjsonMock.mockRejectedValue(expectedError);
|
||||||
|
|
||||||
await expect(importer.importServersFromFile(Mock.of<File>({ type: 'text/html' }))).rejects.toEqual(expectedError);
|
await expect(importer.importServersFromFile(fromPartial({ type: 'text/html' }))).rejects.toEqual(expectedError);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
@@ -57,7 +55,7 @@ describe('ServersImporter', () => {
|
|||||||
])('rejects with error if provided file does not parse to valid list of servers', async (parsedObject) => {
|
])('rejects with error if provided file does not parse to valid list of servers', async (parsedObject) => {
|
||||||
csvjsonMock.mockResolvedValue(parsedObject);
|
csvjsonMock.mockResolvedValue(parsedObject);
|
||||||
|
|
||||||
await expect(importer.importServersFromFile(Mock.of<File>({ type: 'text/html' }))).rejects.toEqual(
|
await expect(importer.importServersFromFile(fromPartial({ type: 'text/html' }))).rejects.toEqual(
|
||||||
new Error('Provided file does not have the right format.'),
|
new Error('Provided file does not have the right format.'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -78,7 +76,7 @@ describe('ServersImporter', () => {
|
|||||||
|
|
||||||
csvjsonMock.mockResolvedValue(expectedServers);
|
csvjsonMock.mockResolvedValue(expectedServers);
|
||||||
|
|
||||||
const result = await importer.importServersFromFile(Mock.all<File>());
|
const result = await importer.importServersFromFile(fromPartial({}));
|
||||||
|
|
||||||
expect(result).toEqual(expectedServers);
|
expect(result).toEqual(expectedServers);
|
||||||
expect(readAsText).toHaveBeenCalledTimes(1);
|
expect(readAsText).toHaveBeenCalledTimes(1);
|
||||||
|
|||||||
@@ -1,25 +1,22 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { RealTimeUpdatesSettings } from '../../src/settings/RealTimeUpdatesSettings';
|
import { RealTimeUpdatesSettings } from '../../src/settings/RealTimeUpdatesSettings';
|
||||||
import type {
|
import type {
|
||||||
RealTimeUpdatesSettings as RealTimeUpdatesSettingsOptions,
|
RealTimeUpdatesSettings as RealTimeUpdatesSettingsOptions,
|
||||||
Settings,
|
|
||||||
} from '../../src/settings/reducers/settings';
|
} from '../../src/settings/reducers/settings';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<RealTimeUpdatesSettings />', () => {
|
describe('<RealTimeUpdatesSettings />', () => {
|
||||||
const toggleRealTimeUpdates = jest.fn();
|
const toggleRealTimeUpdates = vi.fn();
|
||||||
const setRealTimeUpdatesInterval = jest.fn();
|
const setRealTimeUpdatesInterval = vi.fn();
|
||||||
const setUp = (realTimeUpdates: Partial<RealTimeUpdatesSettingsOptions> = {}) => renderWithEvents(
|
const setUp = (realTimeUpdates: Partial<RealTimeUpdatesSettingsOptions> = {}) => renderWithEvents(
|
||||||
<RealTimeUpdatesSettings
|
<RealTimeUpdatesSettings
|
||||||
settings={Mock.of<Settings>({ realTimeUpdates })}
|
settings={fromPartial({ realTimeUpdates })}
|
||||||
toggleRealTimeUpdates={toggleRealTimeUpdates}
|
toggleRealTimeUpdates={toggleRealTimeUpdates}
|
||||||
setRealTimeUpdatesInterval={setRealTimeUpdatesInterval}
|
setRealTimeUpdatesInterval={setRealTimeUpdatesInterval}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('renders enabled real time updates as expected', () => {
|
it('renders enabled real time updates as expected', () => {
|
||||||
setUp({ enabled: true });
|
setUp({ enabled: true });
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { Settings, ShortUrlCreationSettings as ShortUrlsSettings } from '../../src/settings/reducers/settings';
|
import type { ShortUrlCreationSettings as ShortUrlsSettings } from '../../src/settings/reducers/settings';
|
||||||
import { ShortUrlCreationSettings } from '../../src/settings/ShortUrlCreationSettings';
|
import { ShortUrlCreationSettings } from '../../src/settings/ShortUrlCreationSettings';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<ShortUrlCreationSettings />', () => {
|
describe('<ShortUrlCreationSettings />', () => {
|
||||||
const setShortUrlCreationSettings = jest.fn();
|
const setShortUrlCreationSettings = vi.fn();
|
||||||
const setUp = (shortUrlCreation?: ShortUrlsSettings) => renderWithEvents(
|
const setUp = (shortUrlCreation?: ShortUrlsSettings) => renderWithEvents(
|
||||||
<ShortUrlCreationSettings
|
<ShortUrlCreationSettings
|
||||||
settings={Mock.of<Settings>({ shortUrlCreation })}
|
settings={fromPartial({ shortUrlCreation })}
|
||||||
setShortUrlCreationSettings={setShortUrlCreationSettings}
|
setShortUrlCreationSettings={setShortUrlCreationSettings}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[{ validateUrls: true }, true],
|
[{ validateUrls: true }, true],
|
||||||
[{ validateUrls: false }, false],
|
[{ validateUrls: false }, false],
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { Settings, ShortUrlsListSettings as ShortUrlsSettings } from '../../src/settings/reducers/settings';
|
import type { ShortUrlsListSettings as ShortUrlsSettings } from '../../src/settings/reducers/settings';
|
||||||
import { ShortUrlsListSettings } from '../../src/settings/ShortUrlsListSettings';
|
import { ShortUrlsListSettings } from '../../src/settings/ShortUrlsListSettings';
|
||||||
import type { ShortUrlsOrder } from '../../src/short-urls/data';
|
import type { ShortUrlsOrder } from '../../src/short-urls/data';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<ShortUrlsListSettings />', () => {
|
describe('<ShortUrlsListSettings />', () => {
|
||||||
const setSettings = jest.fn();
|
const setSettings = vi.fn();
|
||||||
const setUp = (shortUrlsList?: ShortUrlsSettings) => renderWithEvents(
|
const setUp = (shortUrlsList?: ShortUrlsSettings) => renderWithEvents(
|
||||||
<ShortUrlsListSettings settings={Mock.of<Settings>({ shortUrlsList })} setShortUrlsListSettings={setSettings} />,
|
<ShortUrlsListSettings settings={fromPartial({ shortUrlsList })} setShortUrlsListSettings={setSettings} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[undefined, 'Order by: Created at - DESC'],
|
[undefined, 'Order by: Created at - DESC'],
|
||||||
[{}, 'Order by: Created at - DESC'],
|
[{}, 'Order by: Created at - DESC'],
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { Settings, TagsSettings as TagsSettingsOptions } from '../../src/settings/reducers/settings';
|
import type { TagsSettings as TagsSettingsOptions } from '../../src/settings/reducers/settings';
|
||||||
import { TagsSettings } from '../../src/settings/TagsSettings';
|
import { TagsSettings } from '../../src/settings/TagsSettings';
|
||||||
import type { TagsOrder } from '../../src/tags/data/TagsListChildrenProps';
|
import type { TagsOrder } from '../../src/tags/data/TagsListChildrenProps';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<TagsSettings />', () => {
|
describe('<TagsSettings />', () => {
|
||||||
const setTagsSettings = jest.fn();
|
const setTagsSettings = vi.fn();
|
||||||
const setUp = (tags?: TagsSettingsOptions) => renderWithEvents(
|
const setUp = (tags?: TagsSettingsOptions) => renderWithEvents(
|
||||||
<TagsSettings settings={Mock.of<Settings>({ tags })} setTagsSettings={setTagsSettings} />,
|
<TagsSettings settings={fromPartial({ tags })} setTagsSettings={setTagsSettings} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('renders expected amount of groups', () => {
|
it('renders expected amount of groups', () => {
|
||||||
setUp();
|
setUp();
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { Settings, UiSettings } from '../../src/settings/reducers/settings';
|
import type { UiSettings } from '../../src/settings/reducers/settings';
|
||||||
import { UserInterfaceSettings } from '../../src/settings/UserInterfaceSettings';
|
import { UserInterfaceSettings } from '../../src/settings/UserInterfaceSettings';
|
||||||
import type { Theme } from '../../src/utils/theme';
|
import type { Theme } from '../../src/utils/theme';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<UserInterfaceSettings />', () => {
|
describe('<UserInterfaceSettings />', () => {
|
||||||
const setUiSettings = jest.fn();
|
const setUiSettings = vi.fn();
|
||||||
const setUp = (ui?: UiSettings) => renderWithEvents(
|
const setUp = (ui?: UiSettings) => renderWithEvents(
|
||||||
<UserInterfaceSettings settings={Mock.of<Settings>({ ui })} setUiSettings={setUiSettings} />,
|
<UserInterfaceSettings settings={fromPartial({ ui })} setUiSettings={setUiSettings} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[{ theme: 'dark' as Theme }, true],
|
[{ theme: 'dark' as Theme }, true],
|
||||||
[{ theme: 'light' as Theme }, false],
|
[{ theme: 'light' as Theme }, false],
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { Settings } from '../../src/settings/reducers/settings';
|
import type { Settings } from '../../src/settings/reducers/settings';
|
||||||
import { VisitsSettings } from '../../src/settings/VisitsSettings';
|
import { VisitsSettings } from '../../src/settings/VisitsSettings';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<VisitsSettings />', () => {
|
describe('<VisitsSettings />', () => {
|
||||||
const setVisitsSettings = jest.fn();
|
const setVisitsSettings = vi.fn();
|
||||||
const setUp = (settings: Partial<Settings> = {}) => renderWithEvents(
|
const setUp = (settings: Partial<Settings> = {}) => renderWithEvents(
|
||||||
<VisitsSettings settings={Mock.of<Settings>(settings)} setVisitsSettings={setVisitsSettings} />,
|
<VisitsSettings settings={fromPartial(settings)} setVisitsSettings={setVisitsSettings} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('renders expected components', () => {
|
it('renders expected components', () => {
|
||||||
setUp();
|
setUp();
|
||||||
|
|
||||||
@@ -21,10 +19,10 @@ describe('<VisitsSettings />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[Mock.all<Settings>(), 'Last 30 days'],
|
[fromPartial<Settings>({}), 'Last 30 days'],
|
||||||
[Mock.of<Settings>({ visits: {} }), 'Last 30 days'],
|
[fromPartial<Settings>({ visits: {} }), 'Last 30 days'],
|
||||||
[
|
[
|
||||||
Mock.of<Settings>({
|
fromPartial<Settings>({
|
||||||
visits: {
|
visits: {
|
||||||
defaultInterval: 'last7Days',
|
defaultInterval: 'last7Days',
|
||||||
},
|
},
|
||||||
@@ -32,7 +30,7 @@ describe('<VisitsSettings />', () => {
|
|||||||
'Last 7 days',
|
'Last 7 days',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
Mock.of<Settings>({
|
fromPartial<Settings>({
|
||||||
visits: {
|
visits: {
|
||||||
defaultInterval: 'today',
|
defaultInterval: 'today',
|
||||||
},
|
},
|
||||||
@@ -63,17 +61,17 @@ describe('<VisitsSettings />', () => {
|
|||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[
|
[
|
||||||
Mock.all<Settings>(),
|
fromPartial<Settings>({}),
|
||||||
/The visits coming from potential bots will be included.$/,
|
/The visits coming from potential bots will be included.$/,
|
||||||
/The visits coming from potential bots will be excluded.$/,
|
/The visits coming from potential bots will be excluded.$/,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
Mock.of<Settings>({ visits: { excludeBots: false } }),
|
fromPartial<Settings>({ visits: { excludeBots: false } }),
|
||||||
/The visits coming from potential bots will be included.$/,
|
/The visits coming from potential bots will be included.$/,
|
||||||
/The visits coming from potential bots will be excluded.$/,
|
/The visits coming from potential bots will be excluded.$/,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
Mock.of<Settings>({ visits: { excludeBots: true } }),
|
fromPartial<Settings>({ visits: { excludeBots: true } }),
|
||||||
/The visits coming from potential bots will be excluded.$/,
|
/The visits coming from potential bots will be excluded.$/,
|
||||||
/The visits coming from potential bots will be included.$/,
|
/The visits coming from potential bots will be included.$/,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`<UserInterfaceSettings /> shows different icons based on theme 1`] = `
|
exports[`<UserInterfaceSettings /> > shows different icons based on theme 1`] = `
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="svg-inline--fa fa-moon user-interface__theme-icon"
|
class="svg-inline--fa fa-moon user-interface__theme-icon"
|
||||||
@@ -18,7 +18,7 @@ exports[`<UserInterfaceSettings /> shows different icons based on theme 1`] = `
|
|||||||
</svg>
|
</svg>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<UserInterfaceSettings /> shows different icons based on theme 2`] = `
|
exports[`<UserInterfaceSettings /> > shows different icons based on theme 2`] = `
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="svg-inline--fa fa-sun user-interface__theme-icon"
|
class="svg-inline--fa fa-sun user-interface__theme-icon"
|
||||||
@@ -36,7 +36,7 @@ exports[`<UserInterfaceSettings /> shows different icons based on theme 2`] = `
|
|||||||
</svg>
|
</svg>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<UserInterfaceSettings /> shows different icons based on theme 3`] = `
|
exports[`<UserInterfaceSettings /> > shows different icons based on theme 3`] = `
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="svg-inline--fa fa-sun user-interface__theme-icon"
|
class="svg-inline--fa fa-sun user-interface__theme-icon"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { ShlinkState } from '../../../src/container/types';
|
import type { ShlinkState } from '../../../src/container/types';
|
||||||
import { migrateDeprecatedSettings } from '../../../src/settings/helpers';
|
import { migrateDeprecatedSettings } from '../../../src/settings/helpers';
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ describe('settings-helpers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates settings as expected', () => {
|
it('updates settings as expected', () => {
|
||||||
const state = Mock.of<ShlinkState>({
|
const state = fromPartial<ShlinkState>({
|
||||||
settings: {
|
settings: {
|
||||||
visits: {
|
visits: {
|
||||||
defaultInterval: 'last180days' as any,
|
defaultInterval: 'last180days' as any,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { Settings } from '../../src/settings/reducers/settings';
|
|
||||||
import { CreateShortUrl as createShortUrlsCreator } from '../../src/short-urls/CreateShortUrl';
|
import { CreateShortUrl as createShortUrlsCreator } from '../../src/short-urls/CreateShortUrl';
|
||||||
import type { ShortUrlCreation } from '../../src/short-urls/reducers/shortUrlCreation';
|
import type { ShortUrlCreation } from '../../src/short-urls/reducers/shortUrlCreation';
|
||||||
|
|
||||||
@@ -8,8 +7,8 @@ describe('<CreateShortUrl />', () => {
|
|||||||
const ShortUrlForm = () => <span>ShortUrlForm</span>;
|
const ShortUrlForm = () => <span>ShortUrlForm</span>;
|
||||||
const CreateShortUrlResult = () => <span>CreateShortUrlResult</span>;
|
const CreateShortUrlResult = () => <span>CreateShortUrlResult</span>;
|
||||||
const shortUrlCreation = { validateUrls: true };
|
const shortUrlCreation = { validateUrls: true };
|
||||||
const shortUrlCreationResult = Mock.all<ShortUrlCreation>();
|
const shortUrlCreationResult = fromPartial<ShortUrlCreation>({});
|
||||||
const createShortUrl = jest.fn(async () => Promise.resolve());
|
const createShortUrl = vi.fn(async () => Promise.resolve());
|
||||||
const CreateShortUrl = createShortUrlsCreator(ShortUrlForm, CreateShortUrlResult);
|
const CreateShortUrl = createShortUrlsCreator(ShortUrlForm, CreateShortUrlResult);
|
||||||
const setUp = () => render(
|
const setUp = () => render(
|
||||||
<CreateShortUrl
|
<CreateShortUrl
|
||||||
@@ -17,7 +16,7 @@ describe('<CreateShortUrl />', () => {
|
|||||||
createShortUrl={createShortUrl}
|
createShortUrl={createShortUrl}
|
||||||
selectedServer={null}
|
selectedServer={null}
|
||||||
resetCreateShortUrl={() => {}}
|
resetCreateShortUrl={() => {}}
|
||||||
settings={Mock.of<Settings>({ shortUrlCreation })}
|
settings={fromPartial({ shortUrlCreation })}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import type { Settings } from '../../src/settings/reducers/settings';
|
|
||||||
import type { ShortUrl } from '../../src/short-urls/data';
|
|
||||||
import { EditShortUrl as createEditShortUrl } from '../../src/short-urls/EditShortUrl';
|
import { EditShortUrl as createEditShortUrl } from '../../src/short-urls/EditShortUrl';
|
||||||
import type { ShortUrlDetail } from '../../src/short-urls/reducers/shortUrlDetail';
|
import type { ShortUrlDetail } from '../../src/short-urls/reducers/shortUrlDetail';
|
||||||
import type { ShortUrlEdition } from '../../src/short-urls/reducers/shortUrlEdition';
|
import type { ShortUrlEdition } from '../../src/short-urls/reducers/shortUrlEdition';
|
||||||
@@ -13,12 +11,12 @@ describe('<EditShortUrl />', () => {
|
|||||||
const setUp = (detail: Partial<ShortUrlDetail> = {}, edition: Partial<ShortUrlEdition> = {}) => render(
|
const setUp = (detail: Partial<ShortUrlDetail> = {}, edition: Partial<ShortUrlEdition> = {}) => render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<EditShortUrl
|
<EditShortUrl
|
||||||
settings={Mock.of<Settings>({ shortUrlCreation })}
|
settings={fromPartial({ shortUrlCreation })}
|
||||||
selectedServer={null}
|
selectedServer={null}
|
||||||
shortUrlDetail={Mock.of<ShortUrlDetail>(detail)}
|
shortUrlDetail={fromPartial(detail)}
|
||||||
shortUrlEdition={Mock.of<ShortUrlEdition>(edition)}
|
shortUrlEdition={fromPartial(edition)}
|
||||||
getShortUrlDetail={jest.fn()}
|
getShortUrlDetail={vi.fn()}
|
||||||
editShortUrl={jest.fn(async () => Promise.resolve())}
|
editShortUrl={vi.fn(async () => Promise.resolve())}
|
||||||
/>
|
/>
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
@@ -38,7 +36,7 @@ describe('<EditShortUrl />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders form when detail properly loads', () => {
|
it('renders form when detail properly loads', () => {
|
||||||
setUp({ shortUrl: Mock.of<ShortUrl>({ meta: {} }) });
|
setUp({ shortUrl: fromPartial({ meta: {} }) });
|
||||||
|
|
||||||
expect(screen.getByText('ShortUrlForm')).toBeInTheDocument();
|
expect(screen.getByText('ShortUrlForm')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import type { ShlinkPaginator } from '../../src/api/types';
|
import type { ShlinkPaginator } from '../../src/api/types';
|
||||||
import { Paginator } from '../../src/short-urls/Paginator';
|
import { Paginator } from '../../src/short-urls/Paginator';
|
||||||
import { ELLIPSIS } from '../../src/utils/helpers/pagination';
|
import { ELLIPSIS } from '../../src/utils/helpers/pagination';
|
||||||
|
|
||||||
describe('<Paginator />', () => {
|
describe('<Paginator />', () => {
|
||||||
const buildPaginator = (pagesCount?: number) => Mock.of<ShlinkPaginator>({ pagesCount, currentPage: 1 });
|
const buildPaginator = (pagesCount?: number) => fromPartial<ShlinkPaginator>({ pagesCount, currentPage: 1 });
|
||||||
const setUp = (paginator?: ShlinkPaginator, currentQueryString?: string) => render(
|
const setUp = (paginator?: ShlinkPaginator, currentQueryString?: string) => render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Paginator serverId="abc123" paginator={paginator} currentQueryString={currentQueryString} />
|
<Paginator serverId="abc123" paginator={paginator} currentQueryString={currentQueryString} />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
import type { UserEvent } from '@testing-library/user-event/setup/setup';
|
import type { UserEvent } from '@testing-library/user-event/setup/setup';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { formatISO } from 'date-fns';
|
import { formatISO } from 'date-fns';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import type { ReachableServer, SelectedServer } from '../../src/servers/data';
|
import type { ReachableServer, SelectedServer } from '../../src/servers/data';
|
||||||
import type { Mode } from '../../src/short-urls/ShortUrlForm';
|
import type { Mode } from '../../src/short-urls/ShortUrlForm';
|
||||||
import { ShortUrlForm as createShortUrlForm } from '../../src/short-urls/ShortUrlForm';
|
import { ShortUrlForm as createShortUrlForm } from '../../src/short-urls/ShortUrlForm';
|
||||||
@@ -10,7 +10,7 @@ import type { OptionalString } from '../../src/utils/utils';
|
|||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<ShortUrlForm />', () => {
|
describe('<ShortUrlForm />', () => {
|
||||||
const createShortUrl = jest.fn(async () => Promise.resolve());
|
const createShortUrl = vi.fn(async () => Promise.resolve());
|
||||||
const ShortUrlForm = createShortUrlForm(() => <span>TagsSelector</span>, () => <span>DomainSelector</span>);
|
const ShortUrlForm = createShortUrlForm(() => <span>TagsSelector</span>, () => <span>DomainSelector</span>);
|
||||||
const setUp = (selectedServer: SelectedServer = null, mode: Mode = 'create', title?: OptionalString) =>
|
const setUp = (selectedServer: SelectedServer = null, mode: Mode = 'create', title?: OptionalString) =>
|
||||||
renderWithEvents(
|
renderWithEvents(
|
||||||
@@ -23,8 +23,6 @@ describe('<ShortUrlForm />', () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[
|
[
|
||||||
async (user: UserEvent) => {
|
async (user: UserEvent) => {
|
||||||
@@ -51,7 +49,7 @@ describe('<ShortUrlForm />', () => {
|
|||||||
ios: 'https://ios.com',
|
ios: 'https://ios.com',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Mock.of<ReachableServer>({ version: '3.5.0' }),
|
fromPartial<ReachableServer>({ version: '3.5.0' }),
|
||||||
],
|
],
|
||||||
])('saves short URL with data set in form controls', async (extraFields, extraExpectedValues, selectedServer) => {
|
])('saves short URL with data set in form controls', async (extraFields, extraExpectedValues, selectedServer) => {
|
||||||
const { user } = setUp(selectedServer);
|
const { user } = setUp(selectedServer);
|
||||||
@@ -102,7 +100,7 @@ describe('<ShortUrlForm />', () => {
|
|||||||
[undefined, false, undefined],
|
[undefined, false, undefined],
|
||||||
['old title', false, null],
|
['old title', false, null],
|
||||||
])('sends expected title based on original and new values', async (originalTitle, withNewTitle, expectedSentTitle) => {
|
])('sends expected title based on original and new values', async (originalTitle, withNewTitle, expectedSentTitle) => {
|
||||||
const { user } = setUp(Mock.of<ReachableServer>({ version: '2.6.0' }), 'create', originalTitle);
|
const { user } = setUp(fromPartial({ version: '2.6.0' }), 'create', originalTitle);
|
||||||
|
|
||||||
await user.type(screen.getByPlaceholderText('URL to be shortened'), 'https://long-domain.com/foo/bar');
|
await user.type(screen.getByPlaceholderText('URL to be shortened'), 'https://long-domain.com/foo/bar');
|
||||||
await user.clear(screen.getByPlaceholderText('Title'));
|
await user.clear(screen.getByPlaceholderText('Title'));
|
||||||
@@ -117,10 +115,10 @@ describe('<ShortUrlForm />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[Mock.of<ReachableServer>({ version: '3.0.0' }), false],
|
[fromPartial<ReachableServer>({ version: '3.0.0' }), false],
|
||||||
[Mock.of<ReachableServer>({ version: '3.4.0' }), false],
|
[fromPartial<ReachableServer>({ version: '3.4.0' }), false],
|
||||||
[Mock.of<ReachableServer>({ version: '3.5.0' }), true],
|
[fromPartial<ReachableServer>({ version: '3.5.0' }), true],
|
||||||
[Mock.of<ReachableServer>({ version: '3.6.0' }), true],
|
[fromPartial<ReachableServer>({ version: '3.6.0' }), true],
|
||||||
])('shows device-specific long URLs only for servers supporting it', (selectedServer, fieldsExist) => {
|
])('shows device-specific long URLs only for servers supporting it', (selectedServer, fieldsExist) => {
|
||||||
setUp(selectedServer);
|
setUp(selectedServer);
|
||||||
const placeholders = ['Android-specific redirection', 'iOS-specific redirection', 'Desktop-specific redirection'];
|
const placeholders = ['Android-specific redirection', 'iOS-specific redirection', 'Desktop-specific redirection'];
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
import { screen, waitFor } from '@testing-library/react';
|
import { screen, waitFor } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { endOfDay, formatISO, startOfDay } from 'date-fns';
|
import { endOfDay, formatISO, startOfDay } from 'date-fns';
|
||||||
import { MemoryRouter, useLocation, useNavigate } from 'react-router-dom';
|
import { MemoryRouter, useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import type { ReachableServer, SelectedServer } from '../../src/servers/data';
|
import type { ReachableServer, SelectedServer } from '../../src/servers/data';
|
||||||
import type { Settings } from '../../src/settings/reducers/settings';
|
|
||||||
import { ShortUrlsFilteringBar as filteringBarCreator } from '../../src/short-urls/ShortUrlsFilteringBar';
|
import { ShortUrlsFilteringBar as filteringBarCreator } from '../../src/short-urls/ShortUrlsFilteringBar';
|
||||||
import { formatDate } from '../../src/utils/helpers/date';
|
import { formatDate } from '../../src/utils/helpers/date';
|
||||||
import type { DateRange } from '../../src/utils/helpers/dateIntervals';
|
import type { DateRange } from '../../src/utils/helpers/dateIntervals';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => ({
|
vi.mock('react-router-dom', async () => ({
|
||||||
...jest.requireActual('react-router-dom'),
|
...(await vi.importActual<any>('react-router-dom')),
|
||||||
useParams: jest.fn().mockReturnValue({ serverId: '1' }),
|
useParams: vi.fn().mockReturnValue({ serverId: '1' }),
|
||||||
useNavigate: jest.fn(),
|
useNavigate: vi.fn(),
|
||||||
useLocation: jest.fn().mockReturnValue({}),
|
useLocation: vi.fn().mockReturnValue({}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('<ShortUrlsFilteringBar />', () => {
|
describe('<ShortUrlsFilteringBar />', () => {
|
||||||
const ShortUrlsFilteringBar = filteringBarCreator(() => <>ExportShortUrlsBtn</>, () => <>TagsSelector</>);
|
const ShortUrlsFilteringBar = filteringBarCreator(() => <>ExportShortUrlsBtn</>, () => <>TagsSelector</>);
|
||||||
const navigate = jest.fn();
|
const navigate = vi.fn();
|
||||||
const handleOrderBy = jest.fn();
|
const handleOrderBy = vi.fn();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const setUp = (search = '', selectedServer?: SelectedServer) => {
|
const setUp = (search = '', selectedServer?: SelectedServer) => {
|
||||||
(useLocation as any).mockReturnValue({ search });
|
(useLocation as any).mockReturnValue({ search });
|
||||||
@@ -28,17 +27,15 @@ describe('<ShortUrlsFilteringBar />', () => {
|
|||||||
return renderWithEvents(
|
return renderWithEvents(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<ShortUrlsFilteringBar
|
<ShortUrlsFilteringBar
|
||||||
selectedServer={selectedServer ?? Mock.all<SelectedServer>()}
|
selectedServer={selectedServer ?? fromPartial({})}
|
||||||
order={{}}
|
order={{}}
|
||||||
handleOrderBy={handleOrderBy}
|
handleOrderBy={handleOrderBy}
|
||||||
settings={Mock.of<Settings>({ visits: {} })}
|
settings={fromPartial({ visits: {} })}
|
||||||
/>
|
/>
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('renders expected children components', () => {
|
it('renders expected children components', () => {
|
||||||
setUp();
|
setUp();
|
||||||
|
|
||||||
@@ -74,12 +71,12 @@ describe('<ShortUrlsFilteringBar />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
['tags=foo,bar,baz', Mock.of<ReachableServer>({ version: '3.0.0' }), true],
|
['tags=foo,bar,baz', fromPartial<ReachableServer>({ version: '3.0.0' }), true],
|
||||||
['tags=foo,bar', Mock.of<ReachableServer>({ version: '3.1.0' }), true],
|
['tags=foo,bar', fromPartial<ReachableServer>({ version: '3.1.0' }), true],
|
||||||
['tags=foo', Mock.of<ReachableServer>({ version: '3.0.0' }), false],
|
['tags=foo', fromPartial<ReachableServer>({ version: '3.0.0' }), false],
|
||||||
['', Mock.of<ReachableServer>({ version: '3.0.0' }), false],
|
['', fromPartial<ReachableServer>({ version: '3.0.0' }), false],
|
||||||
['tags=foo,bar,baz', Mock.of<ReachableServer>({ version: '2.10.0' }), false],
|
['tags=foo,bar,baz', fromPartial<ReachableServer>({ version: '2.10.0' }), false],
|
||||||
['', Mock.of<ReachableServer>({ version: '2.10.0' }), false],
|
['', fromPartial<ReachableServer>({ version: '2.10.0' }), false],
|
||||||
])(
|
])(
|
||||||
'renders tags mode toggle if the server supports it and there is more than one tag selected',
|
'renders tags mode toggle if the server supports it and there is more than one tag selected',
|
||||||
(search, selectedServer, shouldHaveComponent) => {
|
(search, selectedServer, shouldHaveComponent) => {
|
||||||
@@ -98,7 +95,7 @@ describe('<ShortUrlsFilteringBar />', () => {
|
|||||||
['&tagsMode=all', 'With all the tags.'],
|
['&tagsMode=all', 'With all the tags.'],
|
||||||
['&tagsMode=any', 'With any of the tags.'],
|
['&tagsMode=any', 'With any of the tags.'],
|
||||||
])('expected tags mode tooltip title', async (initialTagsMode, expectedToggleText) => {
|
])('expected tags mode tooltip title', async (initialTagsMode, expectedToggleText) => {
|
||||||
const { user } = setUp(`tags=foo,bar${initialTagsMode}`, Mock.of<ReachableServer>({ version: '3.0.0' }));
|
const { user } = setUp(`tags=foo,bar${initialTagsMode}`, fromPartial({ version: '3.0.0' }));
|
||||||
|
|
||||||
await user.hover(screen.getByLabelText('Change tags mode'));
|
await user.hover(screen.getByLabelText('Change tags mode'));
|
||||||
expect(await screen.findByRole('tooltip')).toHaveTextContent(expectedToggleText);
|
expect(await screen.findByRole('tooltip')).toHaveTextContent(expectedToggleText);
|
||||||
@@ -109,7 +106,7 @@ describe('<ShortUrlsFilteringBar />', () => {
|
|||||||
['&tagsMode=all', 'tagsMode=any'],
|
['&tagsMode=all', 'tagsMode=any'],
|
||||||
['&tagsMode=any', 'tagsMode=all'],
|
['&tagsMode=any', 'tagsMode=all'],
|
||||||
])('redirects to first page when tags mode changes', async (initialTagsMode, expectedRedirectTagsMode) => {
|
])('redirects to first page when tags mode changes', async (initialTagsMode, expectedRedirectTagsMode) => {
|
||||||
const { user } = setUp(`tags=foo,bar${initialTagsMode}`, Mock.of<ReachableServer>({ version: '3.0.0' }));
|
const { user } = setUp(`tags=foo,bar${initialTagsMode}`, fromPartial({ version: '3.0.0' }));
|
||||||
|
|
||||||
expect(navigate).not.toHaveBeenCalled();
|
expect(navigate).not.toHaveBeenCalled();
|
||||||
await user.click(screen.getByLabelText('Change tags mode'));
|
await user.click(screen.getByLabelText('Change tags mode'));
|
||||||
@@ -127,7 +124,7 @@ describe('<ShortUrlsFilteringBar />', () => {
|
|||||||
['excludePastValidUntil=false', /Exclude enabled in the past/, 'excludePastValidUntil=true'],
|
['excludePastValidUntil=false', /Exclude enabled in the past/, 'excludePastValidUntil=true'],
|
||||||
['excludePastValidUntil=true', /Exclude enabled in the past/, 'excludePastValidUntil=false'],
|
['excludePastValidUntil=true', /Exclude enabled in the past/, 'excludePastValidUntil=false'],
|
||||||
])('allows to toggle filters through filtering dropdown', async (search, menuItemName, expectedQuery) => {
|
])('allows to toggle filters through filtering dropdown', async (search, menuItemName, expectedQuery) => {
|
||||||
const { user } = setUp(search, Mock.of<ReachableServer>({ version: '3.4.0' }));
|
const { user } = setUp(search, fromPartial({ version: '3.4.0' }));
|
||||||
const toggleFilter = async (name: RegExp) => {
|
const toggleFilter = async (name: RegExp) => {
|
||||||
await user.click(screen.getByRole('button', { name: 'Filters' }));
|
await user.click(screen.getByRole('button', { name: 'Filters' }));
|
||||||
await waitFor(() => screen.findByRole('menu'));
|
await waitFor(() => screen.findByRole('menu'));
|
||||||
|
|||||||
@@ -1,36 +1,35 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { MemoryRouter, useNavigate } from 'react-router-dom';
|
import { MemoryRouter, useNavigate } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import type { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub';
|
import type { MercureBoundProps } from '../../src/mercure/helpers/boundToMercureHub';
|
||||||
import type { ReachableServer } from '../../src/servers/data';
|
|
||||||
import type { Settings } from '../../src/settings/reducers/settings';
|
import type { Settings } from '../../src/settings/reducers/settings';
|
||||||
import type { ShortUrl, ShortUrlsOrder } from '../../src/short-urls/data';
|
import type { ShortUrlsOrder } from '../../src/short-urls/data';
|
||||||
import type { ShortUrlsList as ShortUrlsListModel } from '../../src/short-urls/reducers/shortUrlsList';
|
import type { ShortUrlsList as ShortUrlsListModel } from '../../src/short-urls/reducers/shortUrlsList';
|
||||||
import { ShortUrlsList as createShortUrlsList } from '../../src/short-urls/ShortUrlsList';
|
import { ShortUrlsList as createShortUrlsList } from '../../src/short-urls/ShortUrlsList';
|
||||||
import type { ShortUrlsTableType } from '../../src/short-urls/ShortUrlsTable';
|
import type { ShortUrlsTableType } from '../../src/short-urls/ShortUrlsTable';
|
||||||
import type { SemVer } from '../../src/utils/helpers/version';
|
import type { SemVer } from '../../src/utils/helpers/version';
|
||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => ({
|
vi.mock('react-router-dom', async () => ({
|
||||||
...jest.requireActual('react-router-dom'),
|
...(await vi.importActual<any>('react-router-dom')),
|
||||||
useNavigate: jest.fn().mockReturnValue(jest.fn()),
|
useNavigate: vi.fn().mockReturnValue(vi.fn()),
|
||||||
useLocation: jest.fn().mockReturnValue({ search: '?tags=test%20tag&search=example.com' }),
|
useLocation: vi.fn().mockReturnValue({ search: '?tags=test%20tag&search=example.com' }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('<ShortUrlsList />', () => {
|
describe('<ShortUrlsList />', () => {
|
||||||
const ShortUrlsTable: ShortUrlsTableType = ({ onTagClick }) => <span onClick={() => onTagClick?.('foo')}>ShortUrlsTable</span>;
|
const ShortUrlsTable: ShortUrlsTableType = ({ onTagClick }) => <span onClick={() => onTagClick?.('foo')}>ShortUrlsTable</span>;
|
||||||
const ShortUrlsFilteringBar = () => <span>ShortUrlsFilteringBar</span>;
|
const ShortUrlsFilteringBar = () => <span>ShortUrlsFilteringBar</span>;
|
||||||
const listShortUrlsMock = jest.fn();
|
const listShortUrlsMock = vi.fn();
|
||||||
const navigate = jest.fn();
|
const navigate = vi.fn();
|
||||||
const shortUrlsList = Mock.of<ShortUrlsListModel>({
|
const shortUrlsList = fromPartial<ShortUrlsListModel>({
|
||||||
shortUrls: {
|
shortUrls: {
|
||||||
data: [
|
data: [
|
||||||
Mock.of<ShortUrl>({
|
{
|
||||||
shortCode: 'testShortCode',
|
shortCode: 'testShortCode',
|
||||||
shortUrl: 'https://www.example.com/testShortUrl',
|
shortUrl: 'https://www.example.com/testShortUrl',
|
||||||
longUrl: 'https://www.example.com/testLongUrl',
|
longUrl: 'https://www.example.com/testLongUrl',
|
||||||
tags: ['test tag'],
|
tags: ['test tag'],
|
||||||
}),
|
},
|
||||||
],
|
],
|
||||||
pagination: { pagesCount: 3 },
|
pagination: { pagesCount: 3 },
|
||||||
},
|
},
|
||||||
@@ -39,11 +38,11 @@ describe('<ShortUrlsList />', () => {
|
|||||||
const setUp = (settings: Partial<Settings> = {}, version: SemVer = '3.0.0') => renderWithEvents(
|
const setUp = (settings: Partial<Settings> = {}, version: SemVer = '3.0.0') => renderWithEvents(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<ShortUrlsList
|
<ShortUrlsList
|
||||||
{...Mock.of<MercureBoundProps>({ mercureInfo: { loading: true } })}
|
{...fromPartial<MercureBoundProps>({ mercureInfo: { loading: true } })}
|
||||||
listShortUrls={listShortUrlsMock}
|
listShortUrls={listShortUrlsMock}
|
||||||
shortUrlsList={shortUrlsList}
|
shortUrlsList={shortUrlsList}
|
||||||
selectedServer={Mock.of<ReachableServer>({ id: '1', version })}
|
selectedServer={fromPartial({ id: '1', version })}
|
||||||
settings={Mock.of<Settings>(settings)}
|
settings={fromPartial(settings)}
|
||||||
/>
|
/>
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
@@ -52,8 +51,6 @@ describe('<ShortUrlsList />', () => {
|
|||||||
(useNavigate as any).mockReturnValue(navigate);
|
(useNavigate as any).mockReturnValue(navigate);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('wraps expected components', () => {
|
it('wraps expected components', () => {
|
||||||
setUp();
|
setUp();
|
||||||
|
|
||||||
@@ -81,9 +78,9 @@ describe('<ShortUrlsList />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[Mock.of<ShortUrlsOrder>({ field: 'visits', dir: 'ASC' }), 'visits', 'ASC'],
|
[fromPartial<ShortUrlsOrder>({ field: 'visits', dir: 'ASC' }), 'visits', 'ASC'],
|
||||||
[Mock.of<ShortUrlsOrder>({ field: 'title', dir: 'DESC' }), 'title', 'DESC'],
|
[fromPartial<ShortUrlsOrder>({ field: 'title', dir: 'DESC' }), 'title', 'DESC'],
|
||||||
[Mock.all<ShortUrlsOrder>(), undefined, undefined],
|
[fromPartial<ShortUrlsOrder>({}), undefined, undefined],
|
||||||
])('has expected initial ordering based on settings', (defaultOrdering, field, dir) => {
|
])('has expected initial ordering based on settings', (defaultOrdering, field, dir) => {
|
||||||
setUp({ shortUrlsList: { defaultOrdering } });
|
setUp({ shortUrlsList: { defaultOrdering } });
|
||||||
expect(listShortUrlsMock).toHaveBeenCalledWith(expect.objectContaining({
|
expect(listShortUrlsMock).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
@@ -92,23 +89,23 @@ describe('<ShortUrlsList />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[Mock.of<Settings>({
|
[fromPartial<Settings>({
|
||||||
shortUrlsList: {
|
shortUrlsList: {
|
||||||
defaultOrdering: { field: 'visits', dir: 'ASC' },
|
defaultOrdering: { field: 'visits', dir: 'ASC' },
|
||||||
},
|
},
|
||||||
}), '3.3.0' as SemVer, { field: 'visits', dir: 'ASC' }],
|
}), '3.3.0' as SemVer, { field: 'visits', dir: 'ASC' }],
|
||||||
[Mock.of<Settings>({
|
[fromPartial<Settings>({
|
||||||
shortUrlsList: {
|
shortUrlsList: {
|
||||||
defaultOrdering: { field: 'visits', dir: 'ASC' },
|
defaultOrdering: { field: 'visits', dir: 'ASC' },
|
||||||
},
|
},
|
||||||
visits: { excludeBots: true },
|
visits: { excludeBots: true },
|
||||||
}), '3.3.0' as SemVer, { field: 'visits', dir: 'ASC' }],
|
}), '3.3.0' as SemVer, { field: 'visits', dir: 'ASC' }],
|
||||||
[Mock.of<Settings>({
|
[fromPartial<Settings>({
|
||||||
shortUrlsList: {
|
shortUrlsList: {
|
||||||
defaultOrdering: { field: 'visits', dir: 'ASC' },
|
defaultOrdering: { field: 'visits', dir: 'ASC' },
|
||||||
},
|
},
|
||||||
}), '3.4.0' as SemVer, { field: 'visits', dir: 'ASC' }],
|
}), '3.4.0' as SemVer, { field: 'visits', dir: 'ASC' }],
|
||||||
[Mock.of<Settings>({
|
[fromPartial<Settings>({
|
||||||
shortUrlsList: {
|
shortUrlsList: {
|
||||||
defaultOrdering: { field: 'visits', dir: 'ASC' },
|
defaultOrdering: { field: 'visits', dir: 'ASC' },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { fireEvent, screen } from '@testing-library/react';
|
import { fireEvent, screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { ReachableServer, SelectedServer } from '../../src/servers/data';
|
import type { SelectedServer } from '../../src/servers/data';
|
||||||
import type { ShortUrlsOrderableFields } from '../../src/short-urls/data';
|
import type { ShortUrlsOrderableFields } from '../../src/short-urls/data';
|
||||||
import { SHORT_URLS_ORDERABLE_FIELDS } from '../../src/short-urls/data';
|
import { SHORT_URLS_ORDERABLE_FIELDS } from '../../src/short-urls/data';
|
||||||
import type { ShortUrlsList } from '../../src/short-urls/reducers/shortUrlsList';
|
import type { ShortUrlsList } from '../../src/short-urls/reducers/shortUrlsList';
|
||||||
@@ -8,15 +8,13 @@ import { ShortUrlsTable as shortUrlsTableCreator } from '../../src/short-urls/Sh
|
|||||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<ShortUrlsTable />', () => {
|
describe('<ShortUrlsTable />', () => {
|
||||||
const shortUrlsList = Mock.all<ShortUrlsList>();
|
const shortUrlsList = fromPartial<ShortUrlsList>({});
|
||||||
const orderByColumn = jest.fn();
|
const orderByColumn = vi.fn();
|
||||||
const ShortUrlsTable = shortUrlsTableCreator(() => <span>ShortUrlsRow</span>);
|
const ShortUrlsTable = shortUrlsTableCreator(() => <span>ShortUrlsRow</span>);
|
||||||
const setUp = (server: SelectedServer = null) => renderWithEvents(
|
const setUp = (server: SelectedServer = null) => renderWithEvents(
|
||||||
<ShortUrlsTable shortUrlsList={shortUrlsList} selectedServer={server} orderByColumn={() => orderByColumn} />,
|
<ShortUrlsTable shortUrlsList={shortUrlsList} selectedServer={server} orderByColumn={() => orderByColumn} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.resetAllMocks);
|
|
||||||
|
|
||||||
it('should render inner table by default', () => {
|
it('should render inner table by default', () => {
|
||||||
setUp();
|
setUp();
|
||||||
expect(screen.getByRole('table')).toBeInTheDocument();
|
expect(screen.getByRole('table')).toBeInTheDocument();
|
||||||
@@ -56,7 +54,7 @@ describe('<ShortUrlsTable />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should render composed title column', () => {
|
it('should render composed title column', () => {
|
||||||
setUp(Mock.of<ReachableServer>({ version: '2.0.0' }));
|
setUp(fromPartial({ version: '2.0.0' }));
|
||||||
|
|
||||||
const { innerHTML } = screen.getAllByRole('columnheader')[2];
|
const { innerHTML } = screen.getAllByRole('columnheader')[2];
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { ShortUrl } from '../../../src/short-urls/data';
|
|
||||||
import { CreateShortUrlResult as createResult } from '../../../src/short-urls/helpers/CreateShortUrlResult';
|
import { CreateShortUrlResult as createResult } from '../../../src/short-urls/helpers/CreateShortUrlResult';
|
||||||
import type { ShortUrlCreation } from '../../../src/short-urls/reducers/shortUrlCreation';
|
import type { ShortUrlCreation } from '../../../src/short-urls/reducers/shortUrlCreation';
|
||||||
import type { TimeoutToggle } from '../../../src/utils/helpers/hooks';
|
import type { TimeoutToggle } from '../../../src/utils/helpers/hooks';
|
||||||
import { renderWithEvents } from '../../__helpers__/setUpTest';
|
import { renderWithEvents } from '../../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<CreateShortUrlResult />', () => {
|
describe('<CreateShortUrlResult />', () => {
|
||||||
const copyToClipboard = jest.fn();
|
const copyToClipboard = vi.fn();
|
||||||
const useTimeoutToggle = jest.fn(() => [false, copyToClipboard]) as TimeoutToggle;
|
const useTimeoutToggle = vi.fn(() => [false, copyToClipboard]) as TimeoutToggle;
|
||||||
const CreateShortUrlResult = createResult(useTimeoutToggle);
|
const CreateShortUrlResult = createResult(useTimeoutToggle);
|
||||||
const setUp = (creation: ShortUrlCreation) => renderWithEvents(
|
const setUp = (creation: ShortUrlCreation) => renderWithEvents(
|
||||||
<CreateShortUrlResult resetCreateShortUrl={() => {}} creation={creation} />,
|
<CreateShortUrlResult resetCreateShortUrl={() => {}} creation={creation} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('renders an error when error is true', () => {
|
it('renders an error when error is true', () => {
|
||||||
setUp({ error: true, saved: false, saving: false });
|
setUp({ error: true, saved: false, saving: false });
|
||||||
expect(screen.getByText('An error occurred while creating the URL :(')).toBeInTheDocument();
|
expect(screen.getByText('An error occurred while creating the URL :(')).toBeInTheDocument();
|
||||||
@@ -28,14 +25,14 @@ describe('<CreateShortUrlResult />', () => {
|
|||||||
|
|
||||||
it('renders a result message when result is provided', () => {
|
it('renders a result message when result is provided', () => {
|
||||||
setUp(
|
setUp(
|
||||||
{ result: Mock.of<ShortUrl>({ shortUrl: 'https://s.test/abc123' }), saving: false, saved: true, error: false },
|
{ result: fromPartial({ shortUrl: 'https://s.test/abc123' }), saving: false, saved: true, error: false },
|
||||||
);
|
);
|
||||||
expect(screen.getByText(/The short URL is/)).toHaveTextContent('Great! The short URL is https://s.test/abc123');
|
expect(screen.getByText(/The short URL is/)).toHaveTextContent('Great! The short URL is https://s.test/abc123');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Invokes tooltip timeout when copy to clipboard button is clicked', async () => {
|
it('Invokes tooltip timeout when copy to clipboard button is clicked', async () => {
|
||||||
const { user } = setUp(
|
const { user } = setUp(
|
||||||
{ result: Mock.of<ShortUrl>({ shortUrl: 'https://s.test/abc123' }), saving: false, saved: true, error: false },
|
{ result: fromPartial({ shortUrl: 'https://s.test/abc123' }), saving: false, saved: true, error: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(copyToClipboard).not.toHaveBeenCalled();
|
expect(copyToClipboard).not.toHaveBeenCalled();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { screen, waitFor } from '@testing-library/react';
|
import { screen, waitFor } from '@testing-library/react';
|
||||||
import { Mock } from 'ts-mockery';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import type { InvalidShortUrlDeletion, ProblemDetailsError } from '../../../src/api/types/errors';
|
import type { InvalidShortUrlDeletion } from '../../../src/api/types/errors';
|
||||||
import { ErrorTypeV2, ErrorTypeV3 } from '../../../src/api/types/errors';
|
import { ErrorTypeV2, ErrorTypeV3 } from '../../../src/api/types/errors';
|
||||||
import type { ShortUrl } from '../../../src/short-urls/data';
|
import type { ShortUrl } from '../../../src/short-urls/data';
|
||||||
import { DeleteShortUrlModal } from '../../../src/short-urls/helpers/DeleteShortUrlModal';
|
import { DeleteShortUrlModal } from '../../../src/short-urls/helpers/DeleteShortUrlModal';
|
||||||
@@ -9,36 +9,34 @@ import { renderWithEvents } from '../../__helpers__/setUpTest';
|
|||||||
import { TestModalWrapper } from '../../__helpers__/TestModalWrapper';
|
import { TestModalWrapper } from '../../__helpers__/TestModalWrapper';
|
||||||
|
|
||||||
describe('<DeleteShortUrlModal />', () => {
|
describe('<DeleteShortUrlModal />', () => {
|
||||||
const shortUrl = Mock.of<ShortUrl>({
|
const shortUrl = fromPartial<ShortUrl>({
|
||||||
tags: [],
|
tags: [],
|
||||||
shortCode: 'abc123',
|
shortCode: 'abc123',
|
||||||
longUrl: 'https://long-domain.com/foo/bar',
|
longUrl: 'https://long-domain.com/foo/bar',
|
||||||
});
|
});
|
||||||
const deleteShortUrl = jest.fn().mockResolvedValue(undefined);
|
const deleteShortUrl = vi.fn().mockResolvedValue(undefined);
|
||||||
const shortUrlDeleted = jest.fn();
|
const shortUrlDeleted = vi.fn();
|
||||||
const setUp = (shortUrlDeletion: Partial<ShortUrlDeletion>) => renderWithEvents(
|
const setUp = (shortUrlDeletion: Partial<ShortUrlDeletion>) => renderWithEvents(
|
||||||
<TestModalWrapper
|
<TestModalWrapper
|
||||||
renderModal={(args) => (
|
renderModal={(args) => (
|
||||||
<DeleteShortUrlModal
|
<DeleteShortUrlModal
|
||||||
{...args}
|
{...args}
|
||||||
shortUrl={shortUrl}
|
shortUrl={shortUrl}
|
||||||
shortUrlDeletion={Mock.of<ShortUrlDeletion>(shortUrlDeletion)}
|
shortUrlDeletion={fromPartial(shortUrlDeletion)}
|
||||||
deleteShortUrl={deleteShortUrl}
|
deleteShortUrl={deleteShortUrl}
|
||||||
shortUrlDeleted={shortUrlDeleted}
|
shortUrlDeleted={shortUrlDeleted}
|
||||||
resetDeleteShortUrl={jest.fn()}
|
resetDeleteShortUrl={vi.fn()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it('shows generic error when non-threshold error occurs', () => {
|
it('shows generic error when non-threshold error occurs', () => {
|
||||||
setUp({
|
setUp({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: true,
|
error: true,
|
||||||
shortCode: 'abc123',
|
shortCode: 'abc123',
|
||||||
errorData: Mock.of<ProblemDetailsError>({ type: 'OTHER_ERROR' }),
|
errorData: fromPartial({ type: 'OTHER_ERROR' }),
|
||||||
});
|
});
|
||||||
expect(screen.getByText('Something went wrong while deleting the URL :(').parentElement).not.toHaveClass(
|
expect(screen.getByText('Something went wrong while deleting the URL :(').parentElement).not.toHaveClass(
|
||||||
'bg-warning',
|
'bg-warning',
|
||||||
@@ -46,8 +44,8 @@ describe('<DeleteShortUrlModal />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[Mock.of<InvalidShortUrlDeletion>({ type: ErrorTypeV3.INVALID_SHORT_URL_DELETION })],
|
[fromPartial<InvalidShortUrlDeletion>({ type: ErrorTypeV3.INVALID_SHORT_URL_DELETION })],
|
||||||
[Mock.of<InvalidShortUrlDeletion>({ type: ErrorTypeV2.INVALID_SHORT_URL_DELETION })],
|
[fromPartial<InvalidShortUrlDeletion>({ type: ErrorTypeV2.INVALID_SHORT_URL_DELETION })],
|
||||||
])('shows specific error when threshold error occurs', (errorData) => {
|
])('shows specific error when threshold error occurs', (errorData) => {
|
||||||
setUp({ loading: false, error: true, shortCode: 'abc123', errorData });
|
setUp({ loading: false, error: true, shortCode: 'abc123', errorData });
|
||||||
expect(screen.getByText('Something went wrong while deleting the URL :(').parentElement).toHaveClass('bg-warning');
|
expect(screen.getByText('Something went wrong while deleting the URL :(').parentElement).toHaveClass('bg-warning');
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
import { screen } from '@testing-library/react';
|
import { screen } from '@testing-library/react';
|
||||||
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { Mock } from 'ts-mockery';
|
|
||||||
import type { ReportExporter } from '../../../src/common/services/ReportExporter';
|
import type { ReportExporter } from '../../../src/common/services/ReportExporter';
|
||||||
import type { NotFoundServer, ReachableServer, SelectedServer } from '../../../src/servers/data';
|
import type { NotFoundServer, SelectedServer } from '../../../src/servers/data';
|
||||||
|
import type { ShortUrl } from '../../../src/short-urls/data';
|
||||||
import { ExportShortUrlsBtn as createExportShortUrlsBtn } from '../../../src/short-urls/helpers/ExportShortUrlsBtn';
|
import { ExportShortUrlsBtn as createExportShortUrlsBtn } from '../../../src/short-urls/helpers/ExportShortUrlsBtn';
|
||||||
import { renderWithEvents } from '../../__helpers__/setUpTest';
|
import { renderWithEvents } from '../../__helpers__/setUpTest';
|
||||||
|
|
||||||
describe('<ExportShortUrlsBtn />', () => {
|
describe('<ExportShortUrlsBtn />', () => {
|
||||||
const listShortUrls = jest.fn();
|
const listShortUrls = vi.fn();
|
||||||
const buildShlinkApiClient = jest.fn().mockReturnValue({ listShortUrls });
|
const buildShlinkApiClient = vi.fn().mockReturnValue({ listShortUrls });
|
||||||
const exportShortUrls = jest.fn();
|
const exportShortUrls = vi.fn();
|
||||||
const reportExporter = Mock.of<ReportExporter>({ exportShortUrls });
|
const reportExporter = fromPartial<ReportExporter>({ exportShortUrls });
|
||||||
const ExportShortUrlsBtn = createExportShortUrlsBtn(buildShlinkApiClient, reportExporter);
|
const ExportShortUrlsBtn = createExportShortUrlsBtn(buildShlinkApiClient, reportExporter);
|
||||||
const setUp = (amount?: number, selectedServer?: SelectedServer) => renderWithEvents(
|
const setUp = (amount?: number, selectedServer?: SelectedServer) => renderWithEvents(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<ExportShortUrlsBtn selectedServer={selectedServer ?? Mock.all<SelectedServer>()} amount={amount} />
|
<ExportShortUrlsBtn selectedServer={selectedServer ?? fromPartial({})} amount={amount} />
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
|
|
||||||
afterEach(jest.clearAllMocks);
|
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[undefined, '0'],
|
[undefined, '0'],
|
||||||
[1, '1'],
|
[1, '1'],
|
||||||
@@ -31,7 +30,7 @@ describe('<ExportShortUrlsBtn />', () => {
|
|||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[null],
|
[null],
|
||||||
[Mock.of<NotFoundServer>()],
|
[fromPartial<NotFoundServer>({})],
|
||||||
])('does nothing on click if selected server is not reachable', async (selectedServer) => {
|
])('does nothing on click if selected server is not reachable', async (selectedServer) => {
|
||||||
const { user } = setUp(0, selectedServer);
|
const { user } = setUp(0, selectedServer);
|
||||||
|
|
||||||
@@ -49,11 +48,29 @@ describe('<ExportShortUrlsBtn />', () => {
|
|||||||
[385, 20],
|
[385, 20],
|
||||||
])('loads proper amount of pages based on the amount of results', async (amount, expectedPageLoads) => {
|
])('loads proper amount of pages based on the amount of results', async (amount, expectedPageLoads) => {
|
||||||
listShortUrls.mockResolvedValue({ data: [] });
|
listShortUrls.mockResolvedValue({ data: [] });
|
||||||
const { user } = setUp(amount, Mock.of<ReachableServer>({ id: '123' }));
|
const { user } = setUp(amount, fromPartial({ id: '123' }));
|
||||||
|
|
||||||
await user.click(screen.getByRole('button'));
|
await user.click(screen.getByRole('button'));
|
||||||
|
|
||||||
expect(listShortUrls).toHaveBeenCalledTimes(expectedPageLoads);
|
expect(listShortUrls).toHaveBeenCalledTimes(expectedPageLoads);
|
||||||
expect(exportShortUrls).toHaveBeenCalled();
|
expect(exportShortUrls).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('maps short URLs for exporting', async () => {
|
||||||
|
listShortUrls.mockResolvedValue({
|
||||||
|
data: [fromPartial<ShortUrl>({
|
||||||
|
shortUrl: 'https://s.test/short-code',
|
||||||
|
tags: [],
|
||||||
|
})],
|
||||||
|
});
|
||||||
|
const { user } = setUp(undefined, fromPartial({ id: '123' }));
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button'));
|
||||||
|
|
||||||
|
expect(exportShortUrls).toHaveBeenCalledWith([expect.objectContaining({
|
||||||
|
shortUrl: 'https://s.test/short-code',
|
||||||
|
domain: 's.test',
|
||||||
|
shortCode: 'short-code',
|
||||||
|
})]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user