mirror of
https://github.com/ajnart/homarr.git
synced 2026-02-21 05:56:59 +01:00
Merge pull request #766 from ajnart/tests/add-tests
✅ Add vitest and initial tests
This commit is contained in:
11
.eslintrc.js
11
.eslintrc.js
@@ -2,12 +2,12 @@ module.exports = {
|
||||
extends: [
|
||||
'mantine',
|
||||
'plugin:@next/next/recommended',
|
||||
'plugin:jest/recommended',
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:vitest/recommended',
|
||||
],
|
||||
plugins: ['testing-library', 'jest', 'react-hooks', 'react', 'unused-imports'],
|
||||
plugins: ['testing-library', 'react-hooks', 'react', 'unused-imports', 'vitest'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
@@ -31,5 +31,12 @@ module.exports = {
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'no-continue': 'off',
|
||||
'linebreak-style': 0,
|
||||
'vitest/max-nested-describe': [
|
||||
'error',
|
||||
{
|
||||
max: 3,
|
||||
},
|
||||
],
|
||||
'testing-library/no-node-access': ['error', { allowContainerFirstChild: true }],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -104,3 +104,11 @@ Enhancement suggestions are tracked as [GitHub issues](https://github.com/ajnart
|
||||
Homarr uses [GitMoji](https://gitmoji.dev/).
|
||||
We would appreciate it if everyone keeps their commit messages withing these rulings.
|
||||
|
||||
### Tests
|
||||
|
||||
> Components should be tested using unit tests. A unit is the smallest isolated part of the component. Unit tests must not have any dependencies and must be isolated.
|
||||
|
||||
- Place testfiles directly at the root of the unit
|
||||
- Only test a single unit of work inside a unit test
|
||||
- You may test multiple units inside one test file
|
||||
- Testnames do not begin with ``should`` or the unit name
|
||||
@@ -28,12 +28,12 @@ module.exports = {
|
||||
'sk',
|
||||
'no',
|
||||
],
|
||||
localePath: path.resolve('./public/locales'),
|
||||
fallbackLng: 'en',
|
||||
|
||||
localeDetection: true,
|
||||
returnEmptyString: false,
|
||||
debug: false,
|
||||
appendNamespaceToCIMode: true,
|
||||
reloadOnPrerender: process.env.NODE_ENV === 'development',
|
||||
},
|
||||
returnEmptyString: false,
|
||||
appendNamespaceToCIMode: true,
|
||||
reloadOnPrerender: process.env.NODE_ENV === 'development',
|
||||
fallbackLng: 'en',
|
||||
localePath: path.resolve('./public/locales'),
|
||||
};
|
||||
|
||||
23
package.json
23
package.json
@@ -10,18 +10,18 @@
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"turbo" : "turbo run build",
|
||||
"analyze": "ANALYZE=true next build",
|
||||
"turbo": "turbo run build",
|
||||
"start": "next start",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"export": "next build && next export",
|
||||
"lint": "next lint",
|
||||
"jest": "jest",
|
||||
"jest:watch": "jest --watch",
|
||||
"prettier:check": "prettier --check \"**/*.{ts,tsx}\"",
|
||||
"prettier:write": "prettier --write \"**/*.{ts,tsx}\"",
|
||||
"test": "npm run prettier:check && npm run lint && npm run typecheck && npm run jest",
|
||||
"ci": "yarn test && yarn lint --fix && yarn typecheck && yarn prettier:write"
|
||||
"test": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:run": "vitest run",
|
||||
"test:coverage": "vitest run --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ctrl/deluge": "^4.1.0",
|
||||
@@ -44,6 +44,7 @@
|
||||
"@tabler/icons": "^1.106.0",
|
||||
"@tanstack/react-query": "^4.2.1",
|
||||
"@tanstack/react-query-devtools": "^4.24.4",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"axios": "^0.27.2",
|
||||
"consola": "^2.15.3",
|
||||
"cookies-next": "^2.1.1",
|
||||
@@ -71,6 +72,8 @@
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "^12.1.4",
|
||||
"@next/eslint-plugin-next": "^12.1.4",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@types/dockerode": "^3.3.9",
|
||||
"@types/node": "17.0.1",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
@@ -79,23 +82,27 @@
|
||||
"@types/video.js": "^7.3.51",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.7",
|
||||
"@typescript-eslint/parser": "^5.30.7",
|
||||
"@vitest/coverage-c8": "^0.29.3",
|
||||
"@vitest/ui": "^0.29.3",
|
||||
"eslint": "^8.20.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||
"eslint-config-mantine": "^2.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jest": "^26.6.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.6.1",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-testing-library": "^5.5.1",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"jest": "^28.1.3",
|
||||
"eslint-plugin-vitest": "^0.0.54",
|
||||
"happy-dom": "^8.9.0",
|
||||
"prettier": "^2.7.1",
|
||||
"sass": "^1.56.1",
|
||||
"turbo": "^1.8.3",
|
||||
"typescript": "^4.7.4",
|
||||
"video.js": "^8.0.3"
|
||||
"video.js": "^8.0.3",
|
||||
"vitest": "^0.29.3",
|
||||
"vitest-fetch-mock": "^0.2.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "17.0.2",
|
||||
|
||||
13
src/components/AppAvatar.test.tsx
Normal file
13
src/components/AppAvatar.test.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { render, screen, cleanup } from '@testing-library/react';
|
||||
import { describe, expect, it, afterEach } from 'vitest';
|
||||
import { AppAvatar } from './AppAvatar';
|
||||
|
||||
describe('AppAvatar', () => {
|
||||
afterEach(cleanup);
|
||||
|
||||
it('display placeholder when no url', () => {
|
||||
render(<AppAvatar iconUrl="" color="blue" />);
|
||||
|
||||
expect(screen.getByTestId('app-avatar')).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -11,6 +11,7 @@ export const AppAvatar = ({
|
||||
|
||||
return (
|
||||
<Avatar
|
||||
data-testid="app-avatar"
|
||||
src={iconUrl}
|
||||
bg={colorScheme === 'dark' ? colors.gray[8] : colors.gray[2]}
|
||||
size="sm"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useMantineTheme } from '@mantine/core';
|
||||
import { create } from 'zustand';
|
||||
import { useConfigContext } from '../../../../config/provider';
|
||||
import { GridstackBreakpoints } from '../../../../constants/gridstack-breakpoints';
|
||||
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
} from '../../../pages/api/modules/usenet/history';
|
||||
import { UsenetInfoRequestParams, UsenetInfoResponse } from '../../../pages/api/modules/usenet';
|
||||
import { UsenetPauseRequestParams } from '../../../pages/api/modules/usenet/pause';
|
||||
import { queryClient } from '../../../tools/queryClient';
|
||||
import { queryClient } from '../../../tools/server/configurations/tanstack/queryClient.tool';
|
||||
import { UsenetResumeRequestParams } from '../../../pages/api/modules/usenet/resume';
|
||||
|
||||
const POLLING_INTERVAL = 2000;
|
||||
|
||||
@@ -17,8 +17,8 @@ import { useState } from 'react';
|
||||
import { TFunction } from 'react-i18next';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { useConfigContext } from '../../config/provider';
|
||||
import { tryMatchService } from '../../tools/addToHomarr';
|
||||
import { openContextModalGeneric } from '../../tools/mantineModalManagerExtensions';
|
||||
import { MatchingImages, ServiceType, tryMatchPort } from '../../tools/types';
|
||||
import { AppType } from '../../types/app';
|
||||
|
||||
let t: TFunction<'modules/docker', undefined>;
|
||||
@@ -206,3 +206,36 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated legacy code
|
||||
*/
|
||||
function tryMatchType(imageName: string): ServiceType {
|
||||
const match = MatchingImages.find(({ image }) => imageName.includes(image));
|
||||
if (match) {
|
||||
return match.type;
|
||||
}
|
||||
// TODO: Remove this legacy code
|
||||
return 'Other';
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param container the container to match
|
||||
* @returns a new service
|
||||
*/
|
||||
const tryMatchService = (container: Dockerode.ContainerInfo | undefined) => {
|
||||
if (container === undefined) return {};
|
||||
const name = container.Names[0].substring(1);
|
||||
const type = tryMatchType(container.Image);
|
||||
const port = tryMatchPort(type.toLowerCase())?.value ?? container.Ports[0]?.PublicPort;
|
||||
return {
|
||||
name,
|
||||
id: container.Id,
|
||||
type: tryMatchType(container.Image),
|
||||
url: `localhost${port ? `:${port}` : ''}`,
|
||||
icon: `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${name
|
||||
.replace(/\s+/g, '-')
|
||||
.toLowerCase()}.png`,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import {
|
||||
Table,
|
||||
Checkbox,
|
||||
Group,
|
||||
Badge,
|
||||
Checkbox,
|
||||
createStyles,
|
||||
Group,
|
||||
ScrollArea,
|
||||
TextInput,
|
||||
useMantineTheme,
|
||||
Table,
|
||||
Text,
|
||||
TextInput,
|
||||
} from '@mantine/core';
|
||||
import { useElementSize } from '@mantine/hooks';
|
||||
import { IconSearch } from '@tabler/icons';
|
||||
|
||||
@@ -9,8 +9,9 @@ import {
|
||||
} from '@mantine/core';
|
||||
import React from 'react';
|
||||
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
import { GetServerSidePropsContext } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { getServerSideTranslations } from '../tools/server/getServerSideTranslations';
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
root: {
|
||||
@@ -94,12 +95,11 @@ export default function Custom404() {
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps({ locale }: { locale: string }) {
|
||||
export async function getStaticProps({ req, res, locale }: GetServerSidePropsContext) {
|
||||
const translations = await getServerSideTranslations(['common'], locale, undefined, undefined);
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, ['common'])),
|
||||
// Will be passed to the page component as props
|
||||
...translations,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function getServerSideProps({
|
||||
const configPath = path.join(process.cwd(), 'data/configs', `${configName}.json`);
|
||||
const configExists = fs.existsSync(configPath);
|
||||
|
||||
const translations = await getServerSideTranslations(req, res, dashboardNamespaces, locale);
|
||||
const translations = await getServerSideTranslations(dashboardNamespaces, locale, req, res);
|
||||
|
||||
if (!configExists) {
|
||||
// Redirect to 404
|
||||
|
||||
@@ -22,12 +22,12 @@ import { CategoryEditModal } from '../components/Dashboard/Wrappers/Category/Cat
|
||||
import { ConfigProvider } from '../config/provider';
|
||||
import { usePackageAttributesStore } from '../tools/client/zustands/usePackageAttributesStore';
|
||||
import { ColorTheme } from '../tools/color';
|
||||
import { queryClient } from '../tools/queryClient';
|
||||
import { queryClient } from '../tools/server/configurations/tanstack/queryClient.tool';
|
||||
import {
|
||||
getServiceSidePackageAttributes,
|
||||
ServerSidePackageAttributesType,
|
||||
} from '../tools/server/getPackageVersion';
|
||||
import { theme } from '../tools/theme';
|
||||
import { theme } from '../tools/server/theme/theme';
|
||||
|
||||
import { useEditModeInformationStore } from '../hooks/useEditModeInformation';
|
||||
import '../styles/global.scss';
|
||||
|
||||
@@ -9,8 +9,8 @@ import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import Parser from 'rss-parser';
|
||||
|
||||
import { getConfig } from '../../../../tools/config/getConfig';
|
||||
import { Stopwatch } from '../../../../tools/shared/stopwatch';
|
||||
import { IRssWidget } from '../../../../widgets/rss/RssWidgetTile';
|
||||
import { Stopwatch } from '../../../../tools/shared/time/stopwatch.tool';
|
||||
|
||||
type CustomItem = {
|
||||
'media:content': string;
|
||||
|
||||
@@ -46,7 +46,7 @@ export async function getServerSideProps({
|
||||
configName = 'default';
|
||||
}
|
||||
|
||||
const translations = await getServerSideTranslations(req, res, dashboardNamespaces, locale);
|
||||
const translations = await getServerSideTranslations(dashboardNamespaces, locale, req, res);
|
||||
const config = getFrontendConfig(configName as string);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import Dockerode from 'dockerode';
|
||||
|
||||
import { MatchingImages, ServiceType, tryMatchPort } from './types';
|
||||
|
||||
function tryMatchType(imageName: string): ServiceType {
|
||||
// Try to find imageName inside MatchingImages
|
||||
|
||||
const match = MatchingImages.find(({ image }) => imageName.includes(image));
|
||||
if (match) {
|
||||
return match.type;
|
||||
}
|
||||
return 'Other';
|
||||
}
|
||||
|
||||
export function tryMatchService(container: Dockerode.ContainerInfo | undefined) {
|
||||
if (container === undefined) return {};
|
||||
const name = container.Names[0].substring(1);
|
||||
const type = tryMatchType(container.Image);
|
||||
const port = tryMatchPort(type.toLowerCase())?.value ?? container.Ports[0]?.PublicPort;
|
||||
return {
|
||||
name,
|
||||
id: container.Id,
|
||||
type: tryMatchType(container.Image),
|
||||
url: `localhost${port ? `:${port}` : ''}`,
|
||||
icon: `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${name
|
||||
.replace(/\s+/g, '-')
|
||||
.toLowerCase()}.png`,
|
||||
};
|
||||
}
|
||||
@@ -3,17 +3,16 @@ import { IncomingMessage, ServerResponse } from 'http';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
|
||||
export const getServerSideTranslations = async (
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
namespaces: string[],
|
||||
requestLocale?: string
|
||||
requestLocale?: string,
|
||||
req?: IncomingMessage,
|
||||
res?: ServerResponse
|
||||
) => {
|
||||
if (!req || !res) {
|
||||
return serverSideTranslations(requestLocale ?? 'en', namespaces);
|
||||
}
|
||||
|
||||
const configLocale = getCookie('config-locale', { req, res });
|
||||
|
||||
const translations = await serverSideTranslations(
|
||||
(configLocale ?? requestLocale ?? 'en') as string,
|
||||
namespaces
|
||||
);
|
||||
|
||||
return translations;
|
||||
return serverSideTranslations((configLocale ?? requestLocale ?? 'en') as string, namespaces);
|
||||
};
|
||||
|
||||
77
src/tools/server/sdk/plex/plexClient.test.ts
Normal file
77
src/tools/server/sdk/plex/plexClient.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import 'vitest-fetch-mock';
|
||||
|
||||
import { PlexClient } from './plexClient';
|
||||
|
||||
const mockResponse = `<MediaContainer size="1">
|
||||
<Video addedAt="0000000" art="/library/metadata/2/art/00000000" audienceRating="0.0" audienceRatingImage="niceImage" chapterSource="media" contentRating="TV-PG" duration="6262249" guid="plex://movie/0000000000000000" key="/library/metadata/2" lastViewedAt="0000000" librarySectionID="1" librarySectionKey="/library/sections/1" librarySectionTitle="Movies" originalTitle="00000000000000" originallyAvailableAt="0000-00-00" rating="0.0" ratingImage="ratingimage" ratingKey="2" sessionKey="1" studio="Example Studio" summary="Lorem Ispum dolor sit amet" tagline="Yep" thumb="/library/metadata/2/thumb/0000000" title="A long title" titleSort="A short title" type="movie" updatedAt="000000" viewOffset="0" year="0000">
|
||||
<Media audioProfile="ma" id="2" videoProfile="high" audioChannels="2" audioCodec="aac" bitrate="20231" container="mp4" duration="6262249" height="1080" optimizedForStreaming="1" protocol="dash" videoCodec="h264" videoFrameRate="24p" videoResolution="1080p" width="1920" selected="1">
|
||||
<Part audioProfile="ma" hasThumbnail="1" id="2" videoProfile="high" bitrate="20231" container="mp4" duration="6262249" height="1080" optimizedForStreaming="1" protocol="dash" width="1920" decision="transcode" selected="1">
|
||||
<Stream bitDepth="8" bitrate="19975" chromaLocation="left" chromaSubsampling="4:2:0" codec="h264" codedHeight="1088" codedWidth="1920" default="1" displayTitle="XXXX" extendedDisplayTitle="Yes" frameRate="23.975999832153320" hasScalingMatrix="0" height="1080" id="4" level="41" profile="high" refFrames="4" scanType="progressive" streamType="1" title="Example" width="1920" decision="copy" location="segments-video"/>
|
||||
<Stream bitrate="256" bitrateMode="cbr" channels="2" codec="aac" default="1" displayTitle="Not Existing" extendedDisplayTitle="Yes, really" id="5" language="Yep" languageCode="jpn" languageTag="ch" selected="1" streamType="2" decision="transcode" location="segments-audio"/>
|
||||
</Part>
|
||||
</Media>
|
||||
<Genre count="13" filter="genre=48" id="48" tag="Drama"/>
|
||||
<Genre count="8" filter="genre=104" id="104" tag="Adventure"/>
|
||||
<User id="1" thumb="https://google.com" title="example_usr"/>
|
||||
<Player address="0.0.0.0" device="Windows" machineIdentifier="72483785378573857385" model="bundled" platform="Chrome" platformVersion="111.0" product="Plex Web" profile="Web" state="paused" title="Chrome" version="0.000.0" local="1" relayed="0" secure="1" userID="1"/>
|
||||
<Session id="2894294r2jf2038fj3098jgf3gt" bandwidth="21560" location="lan"/>
|
||||
<TranscodeSession key="/transcode/sessions/example-session" throttled="0" complete="0" progress="0" size="-22" speed="18.600000381469727" error="0" duration="100" remaining="70" context="streaming" sourceVideoCodec="h264" sourceAudioCodec="dca" videoDecision="copy" audioDecision="transcode" protocol="dash" container="mp4" videoCodec="h264" audioCodec="aac" audioChannels="2" width="1920" height="1080" transcodeHwRequested="0" transcodeHwFullPipeline="0" timeStamp="1679349635.2791338" maxOffsetAvailable="104.27" minOffsetAvailable="84.166999816894531"/>
|
||||
</Video>
|
||||
</MediaContainer>`;
|
||||
|
||||
describe('Plex SDK', () => {
|
||||
it('abc', async () => {
|
||||
// arrange
|
||||
const client = new PlexClient('https://plex.local', 'MY_TOKEN');
|
||||
|
||||
fetchMock.mockResponseOnce(mockResponse);
|
||||
|
||||
// act
|
||||
const response = await client.getSessions();
|
||||
|
||||
// assert
|
||||
expect(fetchMock.requests().length).toBe(1);
|
||||
expect(fetchMock.requests()[0].url).toBe(
|
||||
'https://plex.local/status/sessions?X-Plex-Token=MY_TOKEN'
|
||||
);
|
||||
expect(response).not.toBeNull();
|
||||
expect(response.length).toBe(1);
|
||||
expect(response[0].id).toBe('2894294r2jf2038fj3098jgf3gt');
|
||||
expect(response[0].username).toBe('example_usr');
|
||||
expect(response[0].userProfilePicture).toBe('https://google.com');
|
||||
expect(response[0].sessionName).toBe('Plex Web (Chrome)');
|
||||
expect(response[0].currentlyPlaying).toMatchObject({
|
||||
name: 'A long title',
|
||||
type: 'movie',
|
||||
metadata: {
|
||||
video: {
|
||||
bitrate: '20231',
|
||||
height: '1080',
|
||||
videoCodec: 'h264',
|
||||
videoFrameRate: '24p',
|
||||
width: '1920',
|
||||
},
|
||||
audio: { audioChannels: '2', audioCodec: 'aac' },
|
||||
transcoding: {
|
||||
audioChannels: '2',
|
||||
audioCodec: 'aac',
|
||||
audioDecision: 'transcode',
|
||||
container: 'mp4',
|
||||
context: 'streaming',
|
||||
duration: '100',
|
||||
error: false,
|
||||
height: '1080',
|
||||
sourceAudioCodec: 'dca',
|
||||
sourceVideoCodec: 'h264',
|
||||
timeStamp: '1679349635.2791338',
|
||||
transcodeHwRequested: false,
|
||||
videoCodec: 'h264',
|
||||
videoDecision: 'copy',
|
||||
width: '1920',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
27
src/tools/shared/math/percentage.tool.test.ts
Normal file
27
src/tools/shared/math/percentage.tool.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { percentage } from './percentage.tool';
|
||||
|
||||
describe('percentage', () => {
|
||||
it.concurrent('be fixed value', () => {
|
||||
// arrange
|
||||
const value = 62;
|
||||
|
||||
// act
|
||||
const fixedPercentage = percentage(value, 100);
|
||||
|
||||
// assert
|
||||
expect(fixedPercentage).toBe('62.0');
|
||||
});
|
||||
|
||||
it.concurrent('be fixed value when decimal places', () => {
|
||||
// arrange
|
||||
const value = 42.69696969;
|
||||
|
||||
// act
|
||||
const fixedPercentage = percentage(value, 100);
|
||||
|
||||
// assert
|
||||
expect(fixedPercentage).toBe('42.7');
|
||||
});
|
||||
});
|
||||
47
src/tools/shared/time/date.tool.test.ts
Normal file
47
src/tools/shared/time/date.tool.test.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { isToday } from './date.tool';
|
||||
|
||||
describe('isToday', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it.concurrent('should return true if date is today', () => {
|
||||
// arrange
|
||||
const date = new Date(2022, 3, 17);
|
||||
vi.setSystemTime(date);
|
||||
|
||||
// act
|
||||
const today = isToday(date);
|
||||
|
||||
// assert
|
||||
expect(today).toBe(true);
|
||||
});
|
||||
|
||||
it.concurrent("should return true if date is today and time doesn't match", () => {
|
||||
// arrange
|
||||
vi.setSystemTime(new Date(2022, 3, 17, 16, 25, 11));
|
||||
|
||||
// act
|
||||
const today = isToday(new Date(2022, 3, 17));
|
||||
|
||||
// assert
|
||||
expect(today).toBe(true);
|
||||
});
|
||||
|
||||
it.concurrent("should be false if date doesn't match", () => {
|
||||
// arrange
|
||||
vi.setSystemTime(new Date(2022, 3, 17, 16));
|
||||
|
||||
// act
|
||||
const today = isToday(new Date(2022, 3, 15));
|
||||
|
||||
// assert
|
||||
expect(today).toBe(false);
|
||||
});
|
||||
});
|
||||
26
src/tools/shared/time/stopwatch.tool.test.ts
Normal file
26
src/tools/shared/time/stopwatch.tool.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { Stopwatch } from './stopwatch.tool';
|
||||
|
||||
describe('stopwatch', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it.concurrent('should be elapsed time between start and current', () => {
|
||||
// arrange
|
||||
vi.setSystemTime(new Date(2023, 2, 26, 0, 0, 0));
|
||||
const stopwatch = new Stopwatch();
|
||||
|
||||
// act
|
||||
vi.setSystemTime(new Date(2023, 2, 26, 0, 0, 2));
|
||||
const milliseconds = stopwatch.getEllapsedMilliseconds();
|
||||
|
||||
// assert
|
||||
expect(milliseconds).toBe(2000);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { MantineTheme } from '@mantine/core';
|
||||
|
||||
import { OptionValues } from '../modules/ModuleTypes';
|
||||
|
||||
export interface Settings {
|
||||
@@ -74,6 +75,12 @@ export type ServiceType =
|
||||
| 'Sabnzbd'
|
||||
| 'NZBGet';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param name the name to match
|
||||
* @param form the form
|
||||
* @returns the port from the map
|
||||
*/
|
||||
export function tryMatchPort(name: string | undefined, form?: any) {
|
||||
if (!name) {
|
||||
return undefined;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, Indicator, IndicatorProps, Popover, useMantineTheme } from '@mantine/core';
|
||||
import { Box, Indicator, IndicatorProps, Popover } from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { isToday } from '../../tools/isToday';
|
||||
import { isToday } from '../../tools/shared/time/date.tool';
|
||||
import { MediaList } from './MediaList';
|
||||
import { MediasType } from './type';
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import { i18n } from 'next-i18next';
|
||||
import { useState } from 'react';
|
||||
import { useConfigContext } from '../../config/provider';
|
||||
import { useColorTheme } from '../../tools/color';
|
||||
import { isToday } from '../../tools/isToday';
|
||||
import { defineWidget } from '../helper';
|
||||
import { IWidget } from '../widgets';
|
||||
import { CalendarDay } from './CalendarDay';
|
||||
|
||||
@@ -4,7 +4,7 @@ import axios from 'axios';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useConfigContext } from '../../config/provider';
|
||||
import { bytes } from '../../tools/bytesHelper';
|
||||
import { percentage } from '../../tools/percentage';
|
||||
import { percentage } from '../../tools/shared/math/percentage.tool';
|
||||
import { DashDotInfo } from './DashDotCompactNetwork';
|
||||
|
||||
interface DashDotCompactStorageProps {
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
Table,
|
||||
Text,
|
||||
Title,
|
||||
useMantineTheme,
|
||||
} from '@mantine/core';
|
||||
import { useElementSize } from '@mantine/hooks';
|
||||
import { IconFileDownload } from '@tabler/icons';
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Group,
|
||||
Select,
|
||||
Stack,
|
||||
Tabs,
|
||||
Text,
|
||||
Title,
|
||||
useMantineTheme,
|
||||
} from '@mantine/core';
|
||||
import { Badge, Button, Group, Select, Stack, Tabs, Text, Title } from '@mantine/core';
|
||||
import { IconFileDownload, IconPlayerPause, IconPlayerPlay } from '@tabler/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
@@ -17,6 +7,7 @@ import dayjs from 'dayjs';
|
||||
import duration from 'dayjs/plugin/duration';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useConfigContext } from '../../config/provider';
|
||||
import { MIN_WIDTH_MOBILE } from '../../constants/constants';
|
||||
import {
|
||||
useGetUsenetInfo,
|
||||
usePauseUsenetQueue,
|
||||
@@ -28,7 +19,6 @@ import { defineWidget } from '../helper';
|
||||
import { IWidget } from '../widgets';
|
||||
import { UsenetHistoryList } from './UsenetHistoryList';
|
||||
import { UsenetQueueList } from './UsenetQueueList';
|
||||
import { MIN_WIDTH_MOBILE } from '../../constants/constants';
|
||||
|
||||
dayjs.extend(duration);
|
||||
|
||||
|
||||
8
tests/setupVitest.ts
Normal file
8
tests/setupVitest.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
//setupVitest.js or similar file
|
||||
import createFetchMock from 'vitest-fetch-mock';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
const fetchMocker = createFetchMock(vi);
|
||||
|
||||
// sets globalThis.fetch and globalThis.fetchMock to our mocked version
|
||||
fetchMocker.enableMocks();
|
||||
18
vitest.config.ts
Normal file
18
vitest.config.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
environment: 'happy-dom',
|
||||
coverage: {
|
||||
provider: 'c8',
|
||||
reporter: ['html'],
|
||||
all: true,
|
||||
exclude: ['.next/', '.yarn/', 'data/'],
|
||||
},
|
||||
setupFiles: ['./tests/setupVitest.ts'],
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user