1
0
mirror of https://github.com/koloml/furbooru-tagging-assistant.git synced 2025-12-23 23:02:58 +00:00

Merge pull request #96 from koloml/feature/vitest

Added Vitest for testing the codebase
This commit is contained in:
2025-02-20 15:52:23 -05:00
committed by GitHub
11 changed files with 2032 additions and 8 deletions

32
.github/workflows/build-and-tests.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Testing
on:
push:
branches:
- master
- 'release/**'
pull_request:
branches:
- master
- 'release/**'
jobs:
run-tests:
name: 'Run Unit Tests'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install npm dependencies
run: npm ci
- name: Building the extension
run: npm run build
- name: Running unit tests
run: npm run test

3
.gitignore vendored
View File

@@ -2,10 +2,11 @@
.DS_Store
node_modules
/build
/coverage
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
vite.config.ts.timestamp-*

1853
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,9 @@
"build:popup": "vite build",
"build:extension": "node build-extension.js",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test": "vitest run --coverage",
"test:watch": "vitest watch --coverage"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^4.0.0",
@@ -15,12 +17,15 @@
"@sveltejs/kit": "^2.17.2",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@types/chrome": "^0.0.304",
"@vitest/coverage-v8": "^3.0.6",
"cheerio": "^1.0.0",
"jsdom": "^26.0.0",
"sass": "^1.85.0",
"svelte": "^5.20.1",
"svelte-check": "^4.1.4",
"typescript": "^5.7.3",
"vite": "^6.1.0"
"vite": "^6.1.0",
"vitest": "^3.0.6"
},
"type": "module",
"dependencies": {

View File

@@ -19,6 +19,7 @@ const config = {
"$styles": "./src/styles",
"$stores": "./src/stores",
"$entities": "./src/lib/extension/entities",
"$tests": "./tests"
},
typescript: {
config: config => {

View File

@@ -0,0 +1,40 @@
import ChromeStorageArea from "$tests/mocks/ChromeStorageArea";
import StorageHelper from "$lib/browser/StorageHelper";
import { expect } from "vitest";
describe('StorageHelper', () => {
let storageAreaMock: ChromeStorageArea;
let storageHelper: StorageHelper;
beforeEach(() => {
storageAreaMock = new ChromeStorageArea();
storageHelper = new StorageHelper(storageAreaMock);
});
it("should return value when data exists", async () => {
const key = 'existingKey';
const value = 'test value';
storageAreaMock.insertMockedData({[key]: value});
expect(await storageHelper.read(key)).toBe(value);
});
it('should return default when data is not present', async () => {
const fallbackValue = 'fallback';
expect(await storageHelper.read('nonexistent', fallbackValue)).toBe(fallbackValue);
});
it('should treat falsy values as existing values', async () => {
const falsyValues = [false, '', 0];
const key = 'testedKey';
const fallbackValue = 'fallback';
for (let testedValue of falsyValues) {
storageAreaMock.insertMockedData({[key]: testedValue});
expect(await storageHelper.read(key, fallbackValue)).toBe(testedValue);
}
});
});

View File

@@ -0,0 +1,9 @@
export default class ChromeEvent<T extends Function> implements chrome.events.Event<T> {
addListener = vi.fn();
getRules = vi.fn();
hasListener = vi.fn();
removeRules = vi.fn();
addRules = vi.fn();
removeListener = vi.fn();
hasListeners = vi.fn();
}

View File

@@ -0,0 +1,5 @@
import ChromeStorageArea from "$tests/mocks/ChromeStorageArea";
export class ChromeLocalStorageArea extends ChromeStorageArea implements chrome.storage.LocalStorageArea {
QUOTA_BYTES = 100000;
}

View File

@@ -0,0 +1,71 @@
import ChromeEvent from "./ChromeEvent";
type ChangedEventCallback = (changes: chrome.storage.StorageChange) => void
export default class ChromeStorageArea implements chrome.storage.StorageArea {
#mockedData: Record<string, any> = {};
getBytesInUse = vi.fn();
clear = vi.fn((): Promise<void> => {
return new Promise(resolve => {
this.#mockedData = {};
resolve();
})
});
set = vi.fn((...args: any[]): Promise<void> => {
return new Promise((resolve, reject) => {
this.#mockedData = Object.assign(this.#mockedData, args[0]);
resolve();
})
});
remove = vi.fn((...args: any[]): Promise<void> => {
return new Promise((resolve, reject) => {
const key = args[0];
if (typeof key === 'string') {
delete this.#mockedData[key];
resolve();
}
reject(new Error('This behavior is not mocked!'));
});
});
get = vi.fn((...args: any[]) => {
return new Promise((resolve, reject) => {
const key = args[0];
if (!key) {
resolve(this.#mockedData);
return;
}
if (typeof key === 'string') {
resolve({[key]: this.#mockedData[key]});
return;
}
if (Array.isArray(key)) {
resolve(
(key as string[]).reduce((entries, key) => {
entries[key] = this.#mockedData[key];
return entries;
}, {} as Record<string, any>)
);
return;
}
reject(new Error('This behavior is not implemented by the mock.'));
});
});
setAccessLevel = vi.fn();
onChanged = new ChromeEvent<ChangedEventCallback>();
getKeys = vi.fn();
insertMockedData(data: Record<string, any>) {
this.#mockedData = data;
}
get mockedData(): Record<string, any> {
return this.#mockedData;
}
}

View File

@@ -11,5 +11,9 @@
"strict": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": false,
"types": [
"vitest/globals",
"@types/chrome",
]
}
}

View File

@@ -1,5 +1,5 @@
import {sveltekit} from '@sveltejs/kit/vite';
import {defineConfig} from 'vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';
export default defineConfig({
build: {
@@ -9,4 +9,13 @@ export default defineConfig({
plugins: [
sveltekit(),
],
test: {
globals: true,
environment: 'jsdom',
exclude: ['**/node_modules/**', '.*\\.d\\.ts$', '.*\\.spec\\.ts$'],
coverage: {
reporter: ['text', 'html'],
include: ['src/lib/**/*.{js,ts}'],
}
}
});