mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-11 16:24:11 +01:00
Frontend: Integrate Vitest test framework #4990
- Includes fixtures and mocks system for API and models as well as npm scripts for running tests, watch mode, coverage and UI - Adds test setup with JSDOM environment and utility function tests - Converts marker model tests from Mocha/Chai to Vitest
This commit is contained in:
2698
frontend/package-lock.json
generated
2698
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,10 @@
|
|||||||
"gettext-extract": "gettext-extract --output src/locales/translations.pot $(find ${SRC:-src} -type f \\( -iname \\*.vue -o -iname \\*.js \\) -not -path src/common/gettext.js)",
|
"gettext-extract": "gettext-extract --output src/locales/translations.pot $(find ${SRC:-src} -type f \\( -iname \\*.vue -o -iname \\*.js \\) -not -path src/common/gettext.js)",
|
||||||
"lint": "eslint --cache src/ *.js",
|
"lint": "eslint --cache src/ *.js",
|
||||||
"test": "karma start",
|
"test": "karma start",
|
||||||
|
"test:vitest": "vitest run",
|
||||||
|
"test:vitest:watch": "vitest",
|
||||||
|
"test:vitest:coverage": "vitest run --coverage",
|
||||||
|
"test:vitest:ui": "vitest --ui",
|
||||||
"testcafe": "testcafe",
|
"testcafe": "testcafe",
|
||||||
"trace": "webpack --stats-children",
|
"trace": "webpack --stats-children",
|
||||||
"upgrade": "npm update && npm audit fix",
|
"upgrade": "npm update && npm audit fix",
|
||||||
@@ -37,6 +41,11 @@
|
|||||||
"@eslint/js": "^9.26.0",
|
"@eslint/js": "^9.26.0",
|
||||||
"@lcdp/offline-plugin": "^5.1.1",
|
"@lcdp/offline-plugin": "^5.1.1",
|
||||||
"@mdi/font": "^7.4.47",
|
"@mdi/font": "^7.4.47",
|
||||||
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
|
"@testing-library/react": "^16.3.0",
|
||||||
|
"@vitejs/plugin-react": "^4.4.1",
|
||||||
|
"@vitest/coverage-v8": "^3.1.3",
|
||||||
|
"@vitest/ui": "^3.1.3",
|
||||||
"@vue/compiler-sfc": "^3.5.13",
|
"@vue/compiler-sfc": "^3.5.13",
|
||||||
"@vue/language-server": "^2.2.10",
|
"@vue/language-server": "^2.2.10",
|
||||||
"@vvo/tzdb": "^6.161.0",
|
"@vvo/tzdb": "^6.161.0",
|
||||||
@@ -52,7 +61,7 @@
|
|||||||
"core-js": "^3.42.0",
|
"core-js": "^3.42.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "^7.1.2",
|
"css-loader": "^7.1.2",
|
||||||
"cssnano": "^7.0.6",
|
"cssnano": "^7.0.7",
|
||||||
"easygettext": "^2.17.0",
|
"easygettext": "^2.17.0",
|
||||||
"eslint": "^9.26.0",
|
"eslint": "^9.26.0",
|
||||||
"eslint-config-prettier": "^10.1.2",
|
"eslint-config-prettier": "^10.1.2",
|
||||||
@@ -60,7 +69,7 @@
|
|||||||
"eslint-plugin-html": "^8.1.2",
|
"eslint-plugin-html": "^8.1.2",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-prettier": "^5.3.1",
|
"eslint-plugin-prettier": "^5.4.0",
|
||||||
"eslint-plugin-promise": "^7.2.1",
|
"eslint-plugin-promise": "^7.2.1",
|
||||||
"eslint-plugin-vue": "^10.1.0",
|
"eslint-plugin-vue": "^10.1.0",
|
||||||
"eslint-plugin-vuetify": "^2.5.2",
|
"eslint-plugin-vuetify": "^2.5.2",
|
||||||
@@ -72,6 +81,7 @@
|
|||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
"hls.js": "^1.6.2",
|
"hls.js": "^1.6.2",
|
||||||
"i": "^0.3.7",
|
"i": "^0.3.7",
|
||||||
|
"jsdom": "^26.1.0",
|
||||||
"karma": "^6.4.4",
|
"karma": "^6.4.4",
|
||||||
"karma-chrome-launcher": "^3.2.0",
|
"karma-chrome-launcher": "^3.2.0",
|
||||||
"karma-coverage-istanbul-reporter": "^3.0.3",
|
"karma-coverage-istanbul-reporter": "^3.0.3",
|
||||||
@@ -80,7 +90,7 @@
|
|||||||
"karma-verbose-reporter": "^0.0.8",
|
"karma-verbose-reporter": "^0.0.8",
|
||||||
"karma-webpack": "^5.0.1",
|
"karma-webpack": "^5.0.1",
|
||||||
"luxon": "^3.6.1",
|
"luxon": "^3.6.1",
|
||||||
"maplibre-gl": "^5.4.0",
|
"maplibre-gl": "^5.5.0",
|
||||||
"memoize-one": "^6.0.0",
|
"memoize-one": "^6.0.0",
|
||||||
"mini-css-extract-plugin": "^2.9.2",
|
"mini-css-extract-plugin": "^2.9.2",
|
||||||
"minimist": ">=1.2.8",
|
"minimist": ">=1.2.8",
|
||||||
@@ -108,6 +118,8 @@
|
|||||||
"tar": "^7.4.3",
|
"tar": "^7.4.3",
|
||||||
"url-loader": "^4.1.1",
|
"url-loader": "^4.1.1",
|
||||||
"util": "^0.12.5",
|
"util": "^0.12.5",
|
||||||
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
|
"vitest": "^3.1.3",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-3-sanitize": "^0.1.4",
|
"vue-3-sanitize": "^0.1.4",
|
||||||
"vue-loader": "^17.4.2",
|
"vue-loader": "^17.4.2",
|
||||||
@@ -118,7 +130,7 @@
|
|||||||
"vue-style-loader": "^4.1.3",
|
"vue-style-loader": "^4.1.3",
|
||||||
"vue3-gettext": "^2.4.0",
|
"vue3-gettext": "^2.4.0",
|
||||||
"vuetify": "^3.8.3",
|
"vuetify": "^3.8.3",
|
||||||
"webpack": "^5.99.7",
|
"webpack": "^5.99.8",
|
||||||
"webpack-bundle-analyzer": "^4.10.2",
|
"webpack-bundle-analyzer": "^4.10.2",
|
||||||
"webpack-cli": "^6.0.1",
|
"webpack-cli": "^6.0.1",
|
||||||
"webpack-hot-middleware": "^2.26.1",
|
"webpack-hot-middleware": "^2.26.1",
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: ["postcss-import", "postcss-preset-env", "cssnano"],
|
plugins: [require("postcss-import"), require("postcss-preset-env"), require("cssnano")],
|
||||||
};
|
};
|
||||||
|
|||||||
108
frontend/tests/vitest/common/util.test.js
Normal file
108
frontend/tests/vitest/common/util.test.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import $util from "common/util";
|
||||||
|
|
||||||
|
describe("$util", () => {
|
||||||
|
describe("formatBytes", () => {
|
||||||
|
it("should format bytes as KB", () => {
|
||||||
|
expect($util.formatBytes(1000)).toBe("1 KB");
|
||||||
|
expect($util.formatBytes(2000)).toBe("2 KB");
|
||||||
|
expect($util.formatBytes("3000")).toBe("3 KB");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should format bytes as MB", () => {
|
||||||
|
expect($util.formatBytes(1048576)).toBe("1.0 MB");
|
||||||
|
expect($util.formatBytes(2097152)).toBe("2.0 MB");
|
||||||
|
expect($util.formatBytes(3145728)).toBe("3.0 MB");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should format bytes as GB", () => {
|
||||||
|
expect($util.formatBytes(1073741824)).toBe("1.0 GB");
|
||||||
|
expect($util.formatBytes(2147483648)).toBe("2.0 GB");
|
||||||
|
expect($util.formatBytes(3221225472)).toBe("3.0 GB");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle zero and falsy values", () => {
|
||||||
|
expect($util.formatBytes(0)).toBe("0 KB");
|
||||||
|
expect($util.formatBytes(null)).toBe("0 KB");
|
||||||
|
expect($util.formatBytes(undefined)).toBe("0 KB");
|
||||||
|
expect($util.formatBytes("")).toBe("0 KB");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("truncate", () => {
|
||||||
|
it("should truncate text longer than specified length", () => {
|
||||||
|
expect($util.truncate("This is a test", 7)).toBe("This i…");
|
||||||
|
expect($util.truncate("Hello world!", 5)).toBe("Hell…");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not truncate text shorter than specified length", () => {
|
||||||
|
expect($util.truncate("Test", 10)).toBe("Test");
|
||||||
|
expect($util.truncate("Short", 10)).toBe("Short");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use custom ending if specified", () => {
|
||||||
|
expect($util.truncate("This is a test", 7, "...")).toBe("This...");
|
||||||
|
expect($util.truncate("Hello world!", 5, " [more]")).toBe(" [more]");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use default values if not specified", () => {
|
||||||
|
expect($util.truncate("This is a very long text that should be truncated")).toBe(
|
||||||
|
"This is a very long text that should be truncated"
|
||||||
|
);
|
||||||
|
// Default length is 100 characters
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("capitalize", () => {
|
||||||
|
it("should capitalize first letter of each word", () => {
|
||||||
|
expect($util.capitalize("hello world")).toBe("Hello World");
|
||||||
|
expect($util.capitalize("test string")).toBe("Test String");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle empty strings", () => {
|
||||||
|
expect($util.capitalize("")).toBe("");
|
||||||
|
expect($util.capitalize(null)).toBe("");
|
||||||
|
expect($util.capitalize(undefined)).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle already capitalized text", () => {
|
||||||
|
expect($util.capitalize("Hello World")).toBe("Hello World");
|
||||||
|
expect($util.capitalize("HELLO WORLD")).toBe("HELLO WORLD");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("ucFirst", () => {
|
||||||
|
it("should capitalize only first letter of string", () => {
|
||||||
|
expect($util.ucFirst("hello world")).toBe("Hello world");
|
||||||
|
expect($util.ucFirst("test string")).toBe("Test string");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle empty strings", () => {
|
||||||
|
expect($util.ucFirst("")).toBe("");
|
||||||
|
expect($util.ucFirst(null)).toBe("");
|
||||||
|
expect($util.ucFirst(undefined)).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle already capitalized text", () => {
|
||||||
|
expect($util.ucFirst("Hello world")).toBe("Hello world");
|
||||||
|
expect($util.ucFirst("HELLO world")).toBe("HELLO world");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("formatSeconds", () => {
|
||||||
|
it("should format seconds as mm:ss", () => {
|
||||||
|
expect($util.formatSeconds(0)).toBe("0:00");
|
||||||
|
expect($util.formatSeconds(1)).toBe("0:01");
|
||||||
|
expect($util.formatSeconds(10)).toBe("0:10");
|
||||||
|
expect($util.formatSeconds(60)).toBe("1:00");
|
||||||
|
expect($util.formatSeconds(65)).toBe("1:05");
|
||||||
|
expect($util.formatSeconds(125)).toBe("2:05");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle negative or falsy values", () => {
|
||||||
|
expect($util.formatSeconds(-1)).toBe("0:00");
|
||||||
|
expect($util.formatSeconds(null)).toBe("0:00");
|
||||||
|
expect($util.formatSeconds(undefined)).toBe("0:00");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
100
frontend/tests/vitest/config.js
Normal file
100
frontend/tests/vitest/config.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
export default {
|
||||||
|
mode: "user",
|
||||||
|
name: "PhotoPrism",
|
||||||
|
about: "PhotoPrism® CE",
|
||||||
|
edition: "ce",
|
||||||
|
version: "210710-bae1f2d7-Linux-x86_64-DEBUG",
|
||||||
|
copyright: "(c) 2018-2025 PhotoPrism UG. All rights reserved.",
|
||||||
|
flags: "public debug develop experimental settings",
|
||||||
|
baseUri: "",
|
||||||
|
staticUri: "/static",
|
||||||
|
apiUri: "/api/v1",
|
||||||
|
contentUri: "/api/v1",
|
||||||
|
siteUrl: "http://localhost:2342/",
|
||||||
|
sitePreview: "http://localhost:2342/static/img/preview.jpg",
|
||||||
|
siteTitle: "PhotoPrism",
|
||||||
|
siteCaption: "AI-Powered Photos App",
|
||||||
|
siteDescription: "Open-Source Photo Management",
|
||||||
|
siteAuthor: "@photoprism_app",
|
||||||
|
debug: false,
|
||||||
|
readonly: false,
|
||||||
|
uploadNSFW: false,
|
||||||
|
public: false,
|
||||||
|
develop: true,
|
||||||
|
experimental: true,
|
||||||
|
disableSettings: false,
|
||||||
|
test: true,
|
||||||
|
demo: false,
|
||||||
|
sponsor: true,
|
||||||
|
albumCategories: ["Animal", "Holiday"],
|
||||||
|
albums: [
|
||||||
|
{
|
||||||
|
ID: 69,
|
||||||
|
UID: "aqw0vmr32zb4560f",
|
||||||
|
Slug: "test-album-1",
|
||||||
|
Type: "album",
|
||||||
|
Title: "Test Album 1",
|
||||||
|
Favorite: true,
|
||||||
|
Private: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 70,
|
||||||
|
UID: "aqw0vmzrkc202vty",
|
||||||
|
Slug: "test-album-2",
|
||||||
|
Type: "album",
|
||||||
|
Title: "Test Album 2",
|
||||||
|
Favorite: true,
|
||||||
|
Private: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
cameras: [
|
||||||
|
{
|
||||||
|
ID: 7,
|
||||||
|
Slug: "apple-iphone-se",
|
||||||
|
Name: "Apple iPhone SE",
|
||||||
|
Make: "Apple",
|
||||||
|
Model: "iPhone SE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
Slug: "canon-eos-6d",
|
||||||
|
Name: "Canon EOS 6D",
|
||||||
|
Make: "Canon",
|
||||||
|
Model: "EOS 6D",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
mapKey: "D9ve6edlcVR2mEsNvCXa",
|
||||||
|
downloadToken: "2lbh9x09",
|
||||||
|
previewToken: "public",
|
||||||
|
settings: {
|
||||||
|
ui: {
|
||||||
|
scrollbar: true,
|
||||||
|
zoom: false,
|
||||||
|
theme: "default",
|
||||||
|
language: "en",
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
batchSize: 90,
|
||||||
|
},
|
||||||
|
maps: {
|
||||||
|
animate: 0,
|
||||||
|
style: "streets",
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
upload: true,
|
||||||
|
download: true,
|
||||||
|
private: true,
|
||||||
|
review: false,
|
||||||
|
files: true,
|
||||||
|
videos: true,
|
||||||
|
folders: true,
|
||||||
|
albums: true,
|
||||||
|
moments: true,
|
||||||
|
places: true,
|
||||||
|
edit: true,
|
||||||
|
share: true,
|
||||||
|
library: true,
|
||||||
|
import: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
138
frontend/tests/vitest/fixtures.js
Normal file
138
frontend/tests/vitest/fixtures.js
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { vi } from "vitest";
|
||||||
|
import { Settings } from "luxon";
|
||||||
|
|
||||||
|
Settings.defaultLocale = "en";
|
||||||
|
Settings.defaultZoneName = "UTC";
|
||||||
|
|
||||||
|
// Mock Config
|
||||||
|
export const mockConfig = {
|
||||||
|
contentUri: "/api/v1",
|
||||||
|
previewToken: "public",
|
||||||
|
apiUri: "/api/v1",
|
||||||
|
baseUri: "",
|
||||||
|
staticUri: "/static",
|
||||||
|
downloadToken: "2lbh9x09",
|
||||||
|
mode: "user",
|
||||||
|
debug: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock RestModel
|
||||||
|
export class MockRestModel {
|
||||||
|
constructor(values) {
|
||||||
|
this.__originalValues = {};
|
||||||
|
this.setValues(values || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
setValues(values) {
|
||||||
|
if (!values) return this;
|
||||||
|
|
||||||
|
for (let key in values) {
|
||||||
|
if (values.hasOwnProperty(key)) {
|
||||||
|
this[key] = values[key];
|
||||||
|
this.__originalValues[key] = values[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
getId() {
|
||||||
|
return this.UID || this.ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
getValues() {
|
||||||
|
return { ...this.__originalValues };
|
||||||
|
}
|
||||||
|
|
||||||
|
getEntityResource() {
|
||||||
|
return `${this.constructor.getCollectionResource()}/${this.getId()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
return Promise.resolve({ success: "ok" });
|
||||||
|
}
|
||||||
|
|
||||||
|
static getCollectionResource() {
|
||||||
|
return "items";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API Response Helpers
|
||||||
|
export const mockApiResponse = (data) => {
|
||||||
|
return { data };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockPutResponse = (data = { success: "ok" }) => {
|
||||||
|
return vi.fn().mockResolvedValue(mockApiResponse(data));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockDeleteResponse = (data = { success: "ok" }) => {
|
||||||
|
return vi.fn().mockResolvedValue(mockApiResponse(data));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockGetResponse = (data) => {
|
||||||
|
return vi.fn().mockResolvedValue(mockApiResponse(data));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockPostResponse = (data = { success: "ok" }) => {
|
||||||
|
return vi.fn().mockResolvedValue(mockApiResponse(data));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Global mock variables
|
||||||
|
export const apiMock = {
|
||||||
|
put: mockPutResponse(),
|
||||||
|
delete: mockDeleteResponse(),
|
||||||
|
get: mockGetResponse(),
|
||||||
|
post: mockPostResponse(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Setup common mocks
|
||||||
|
export const setupCommonMocks = () => {
|
||||||
|
// Mock Model
|
||||||
|
vi.mock("model/rest", () => ({
|
||||||
|
default: MockRestModel,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock API
|
||||||
|
vi.mock("common/api", () => ({
|
||||||
|
default: apiMock,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock session
|
||||||
|
vi.mock("app/session", () => ({
|
||||||
|
$config: mockConfig,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock gettext
|
||||||
|
vi.mock("common/gettext", () => ({
|
||||||
|
$gettext: vi.fn((text) => text),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Setup common headers
|
||||||
|
export const mockHeaders = {
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setupMarkerMocks = () => {
|
||||||
|
apiMock.put.mockImplementation((url, data) => {
|
||||||
|
if (url.includes("markers/mBC123ghytr")) {
|
||||||
|
return Promise.resolve({ data: { success: "ok" } });
|
||||||
|
} else if (url.includes("markers/mCC123ghytr")) {
|
||||||
|
return Promise.resolve({ data: { success: "ok" } });
|
||||||
|
} else if (url.includes("markers/mDC123ghytr")) {
|
||||||
|
return Promise.resolve({ data: { success: "ok", Name: "testname" } });
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve({ data: { success: "ok" } });
|
||||||
|
});
|
||||||
|
|
||||||
|
apiMock.delete.mockImplementation((url) => {
|
||||||
|
if (url.includes("markers/mEC123ghytr/subject")) {
|
||||||
|
return Promise.resolve({ data: { success: "ok" } });
|
||||||
|
}
|
||||||
|
return Promise.resolve({ data: { success: "ok" } });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default { setupCommonMocks, setupMarkerMocks };
|
||||||
213
frontend/tests/vitest/model/marker.test.js
Normal file
213
frontend/tests/vitest/model/marker.test.js
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import { describe, it, expect, beforeEach } from "vitest";
|
||||||
|
import { Marker, BatchSize } from "model/marker";
|
||||||
|
import { setupMarkerMocks } from "../fixtures";
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
setupMarkerMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("model/marker", () => {
|
||||||
|
it("should get marker defaults", () => {
|
||||||
|
const values = { FileUID: "fghjojp" };
|
||||||
|
const marker = new Marker(values);
|
||||||
|
const result = marker.getDefaults();
|
||||||
|
expect(result.UID).toBe("");
|
||||||
|
expect(result.FileUID).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should get route view", () => {
|
||||||
|
const values = { UID: "ABC123ghytr", FileUID: "fhjouohnnmnd", Type: "face", Src: "image" };
|
||||||
|
const marker = new Marker(values);
|
||||||
|
const result = marker.route("test");
|
||||||
|
expect(result.name).toBe("test");
|
||||||
|
expect(result.query.q).toBe("marker:ABC123ghytr");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return classes", () => {
|
||||||
|
const values = { UID: "ABC123ghytr", FileUID: "fhjouohnnmnd", Type: "face", Src: "image" };
|
||||||
|
const marker = new Marker(values);
|
||||||
|
const result = marker.classes(true);
|
||||||
|
expect(result).toContain("is-marker");
|
||||||
|
expect(result).toContain("uid-ABC123ghytr");
|
||||||
|
expect(result).toContain("is-selected");
|
||||||
|
expect(result).not.toContain("is-review");
|
||||||
|
expect(result).not.toContain("is-invalid");
|
||||||
|
|
||||||
|
const result2 = marker.classes(false);
|
||||||
|
expect(result2).toContain("is-marker");
|
||||||
|
expect(result2).toContain("uid-ABC123ghytr");
|
||||||
|
expect(result2).not.toContain("is-selected");
|
||||||
|
expect(result2).not.toContain("is-review");
|
||||||
|
expect(result2).not.toContain("is-invalid");
|
||||||
|
|
||||||
|
const values2 = {
|
||||||
|
UID: "mBC123ghytr",
|
||||||
|
FileUID: "fhjouohnnmnd",
|
||||||
|
Type: "face",
|
||||||
|
Src: "image",
|
||||||
|
Invalid: true,
|
||||||
|
Review: true,
|
||||||
|
};
|
||||||
|
const marker2 = new Marker(values2);
|
||||||
|
const result3 = marker2.classes(true);
|
||||||
|
expect(result3).toContain("is-marker");
|
||||||
|
expect(result3).toContain("uid-mBC123ghytr");
|
||||||
|
expect(result3).toContain("is-selected");
|
||||||
|
expect(result3).toContain("is-review");
|
||||||
|
expect(result3).toContain("is-invalid");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should get marker entity name", () => {
|
||||||
|
const values = {
|
||||||
|
UID: "ABC123ghytr",
|
||||||
|
FileUID: "fhjouohnnmnd",
|
||||||
|
Type: "face",
|
||||||
|
Src: "image",
|
||||||
|
Name: "test",
|
||||||
|
};
|
||||||
|
const marker = new Marker(values);
|
||||||
|
const result = marker.getEntityName();
|
||||||
|
expect(result).toBe("test");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should get marker title", () => {
|
||||||
|
const values = {
|
||||||
|
UID: "ABC123ghytr",
|
||||||
|
FileUID: "fhjouohnnmnd",
|
||||||
|
Type: "face",
|
||||||
|
Src: "image",
|
||||||
|
Name: "test",
|
||||||
|
};
|
||||||
|
const marker = new Marker(values);
|
||||||
|
const result = marker.getTitle();
|
||||||
|
expect(result).toBe("test");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should get thumbnail url", () => {
|
||||||
|
const values = { UID: "ABC123ghytr", FileUID: "fhjouohnnmnd", Type: "face", Src: "image" };
|
||||||
|
const marker = new Marker(values);
|
||||||
|
const result = marker.thumbnailUrl("xyz");
|
||||||
|
expect(result).toBe("/api/v1/svg/portrait");
|
||||||
|
|
||||||
|
const values2 = {
|
||||||
|
UID: "ABC123ghytr",
|
||||||
|
FileUID: "fhjouohnnmnd",
|
||||||
|
Type: "face",
|
||||||
|
Src: "image",
|
||||||
|
Thumb: "nicethumbuid",
|
||||||
|
};
|
||||||
|
const marker2 = new Marker(values2);
|
||||||
|
const result2 = marker2.thumbnailUrl();
|
||||||
|
expect(result2).toBe("/api/v1/t/nicethumbuid/public/tile_160");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should get date string", () => {
|
||||||
|
const values = {
|
||||||
|
UID: "ABC123ghytr",
|
||||||
|
FileUID: "fhjouohnnmnd",
|
||||||
|
Type: "face",
|
||||||
|
Src: "image",
|
||||||
|
CreatedAt: "2012-07-08T14:45:39Z",
|
||||||
|
};
|
||||||
|
const marker = new Marker(values);
|
||||||
|
const result = marker.getDateString();
|
||||||
|
expect(result).toBe("2023-10-01 10:00:00");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should approve marker", () => {
|
||||||
|
const values = {
|
||||||
|
UID: "mBC123ghytr",
|
||||||
|
FileUID: "fhjouohnnmnd",
|
||||||
|
Type: "face",
|
||||||
|
Src: "image",
|
||||||
|
Invalid: true,
|
||||||
|
Review: true,
|
||||||
|
};
|
||||||
|
const marker = new Marker(values);
|
||||||
|
expect(marker.Review).toBe(true);
|
||||||
|
expect(marker.Invalid).toBe(true);
|
||||||
|
marker.approve();
|
||||||
|
expect(marker.Review).toBe(false);
|
||||||
|
expect(marker.Invalid).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should reject marker", () => {
|
||||||
|
const values = {
|
||||||
|
UID: "mCC123ghytr",
|
||||||
|
FileUID: "fhjouohnnmnd",
|
||||||
|
Type: "face",
|
||||||
|
Src: "image",
|
||||||
|
Invalid: false,
|
||||||
|
Review: true,
|
||||||
|
};
|
||||||
|
const marker = new Marker(values);
|
||||||
|
expect(marker.Review).toBe(true);
|
||||||
|
expect(marker.Invalid).toBe(false);
|
||||||
|
marker.reject();
|
||||||
|
expect(marker.Review).toBe(false);
|
||||||
|
expect(marker.Invalid).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should rename marker", async () => {
|
||||||
|
const values = {
|
||||||
|
UID: "mDC123ghytr",
|
||||||
|
FileUID: "fhjouohnnmnd",
|
||||||
|
Type: "face",
|
||||||
|
Src: "image",
|
||||||
|
Subject: "skhljkpigh",
|
||||||
|
Name: "",
|
||||||
|
SubjSrc: "manual",
|
||||||
|
};
|
||||||
|
const marker = new Marker(values);
|
||||||
|
expect(marker.Name).toBe("");
|
||||||
|
marker.setName();
|
||||||
|
expect(marker.Name).toBe("");
|
||||||
|
|
||||||
|
const values2 = {
|
||||||
|
UID: "mDC123ghytr",
|
||||||
|
FileUID: "fhjouohnnmnd",
|
||||||
|
Type: "face",
|
||||||
|
Src: "image",
|
||||||
|
Subject: "skhljkpigh",
|
||||||
|
Name: "testname",
|
||||||
|
SubjSrc: "manual",
|
||||||
|
};
|
||||||
|
const marker2 = new Marker(values2);
|
||||||
|
expect(marker2.Name).toBe("testname");
|
||||||
|
|
||||||
|
const response = await marker2.setName();
|
||||||
|
expect(response.success).toBe("ok");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should clear subject", async () => {
|
||||||
|
const values = {
|
||||||
|
UID: "mEC123ghytr",
|
||||||
|
FileUID: "fhjouohnnmnd",
|
||||||
|
Type: "face",
|
||||||
|
Src: "image",
|
||||||
|
Subject: "skhljkpigh",
|
||||||
|
Name: "testname",
|
||||||
|
SubjSrc: "manual",
|
||||||
|
};
|
||||||
|
const marker = new Marker(values);
|
||||||
|
const response = await marker.clearSubject();
|
||||||
|
expect(response.success).toBe("ok");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return batch size", () => {
|
||||||
|
expect(Marker.batchSize()).toBe(BatchSize);
|
||||||
|
Marker.setBatchSize(30);
|
||||||
|
expect(Marker.batchSize()).toBe(30);
|
||||||
|
Marker.setBatchSize(BatchSize);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should get collection resource", () => {
|
||||||
|
const result = Marker.getCollectionResource();
|
||||||
|
expect(result).toBe("markers");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should get model name", () => {
|
||||||
|
const result = Marker.getModelName();
|
||||||
|
expect(result).toBe("Marker");
|
||||||
|
});
|
||||||
|
});
|
||||||
59
frontend/tests/vitest/setup.js
Normal file
59
frontend/tests/vitest/setup.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import "@testing-library/jest-dom";
|
||||||
|
import { cleanup } from "@testing-library/react";
|
||||||
|
import { afterEach, vi, beforeAll } from "vitest";
|
||||||
|
import { setupCommonMocks } from "./fixtures";
|
||||||
|
|
||||||
|
global.window = global.window || {};
|
||||||
|
global.window.__CONFIG__ = {
|
||||||
|
debug: false,
|
||||||
|
trace: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
global.window.location = {
|
||||||
|
protocol: "https:",
|
||||||
|
};
|
||||||
|
|
||||||
|
global.navigator = {
|
||||||
|
userAgent: "node.js",
|
||||||
|
maxTouchPoints: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
setupCommonMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock("luxon", () => ({
|
||||||
|
DateTime: {
|
||||||
|
fromISO: vi.fn().mockReturnValue({
|
||||||
|
toLocaleString: vi.fn().mockReturnValue("2023-10-01 10:00:00"),
|
||||||
|
}),
|
||||||
|
DATETIME_MED: {},
|
||||||
|
DATETIME_MED_WITH_WEEKDAY: {},
|
||||||
|
DATE_MED: {},
|
||||||
|
TIME_24_SIMPLE: {},
|
||||||
|
},
|
||||||
|
Settings: {
|
||||||
|
defaultLocale: "en",
|
||||||
|
defaultZoneName: "UTC",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("common/gettext", () => ({
|
||||||
|
$gettext: vi.fn((text) => text),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("app/session", () => ({
|
||||||
|
$config: {},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("common/notify", () => ({
|
||||||
|
default: {
|
||||||
|
success: vi.fn(),
|
||||||
|
warn: vi.fn(),
|
||||||
|
error: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
27
frontend/vitest.config.js
Normal file
27
frontend/vitest.config.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import tsconfigPaths from "vite-tsconfig-paths";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react(), tsconfigPaths()],
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: "jsdom",
|
||||||
|
setupFiles: "./tests/vitest/setup.js",
|
||||||
|
include: ["tests/vitest/**/*.{test,spec}.{js,jsx}"],
|
||||||
|
coverage: {
|
||||||
|
reporter: ["text", "html"],
|
||||||
|
include: ["src/**/*.{js,jsx}"],
|
||||||
|
exclude: ["src/locales/**"],
|
||||||
|
},
|
||||||
|
alias: {
|
||||||
|
app: path.resolve(__dirname, "./src/app"),
|
||||||
|
common: path.resolve(__dirname, "./src/common"),
|
||||||
|
component: path.resolve(__dirname, "./src/component"),
|
||||||
|
model: path.resolve(__dirname, "./src/model"),
|
||||||
|
options: path.resolve(__dirname, "./src/options"),
|
||||||
|
page: path.resolve(__dirname, "./src/page"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user