diff --git a/src/constants/i18n.ts b/src/constants/i18n.ts new file mode 100644 index 00000000..1d0e015b --- /dev/null +++ b/src/constants/i18n.ts @@ -0,0 +1,5 @@ +export const defaultLanguage = "en" as const; + +export const supportedLanguages = ["it", "en"] as const; + +export type SupportedLanguages = (typeof supportedLanguages)[number]; diff --git a/src/data/firestore/common/create.spec.ts b/src/data/firestore/common/create.spec.ts index acb7786a..a807a1cd 100644 --- a/src/data/firestore/common/create.spec.ts +++ b/src/data/firestore/common/create.spec.ts @@ -1,7 +1,7 @@ -import { test, describe, expect } from "vitest"; -import { createDocument } from "./create"; -import { getDocumentById } from "./get-by-id"; -import { testConverter, type TestDoc, type TestModel } from "./test-model"; +import { describe, expect, test } from "vitest"; +import createDocument from "./create"; +import { defaultLanguage } from "~/constants/i18n"; +import {testConverter, type TestDoc, type TestModel} from "./test-model"; import type { FirestoreDataConverter } from "firebase-admin/firestore"; const testCollection = "create-test"; @@ -11,29 +11,18 @@ describe("Create a new document", () => { const modelToSave: TestModel = { lastUpdate: new Date("2024-01-01"), name: "Test Document", + description: "Test description", }; const result = await createDocument( testCollection, testConverter, modelToSave, + defaultLanguage, ); expect(result).toMatchObject({ status: "success", data: expect.any(String), }); - - const getResult = await getDocumentById( - testCollection, - testConverter, - result.data as string, - ); - expect(getResult).toMatchObject({ - status: "success", - data: expect.objectContaining({ - ...modelToSave, - id: result.data, - }), - }); }); test("Should return an error if id is present", async () => { @@ -46,6 +35,7 @@ describe("Create a new document", () => { testCollection, testConverter, modelToSave, + defaultLanguage, ); expect(result).toMatchObject({ status: "error", @@ -73,6 +63,7 @@ describe("Create a new document", () => { testCollection, fakeConverter, modelToSave, + defaultLanguage, ); expect(result).toMatchObject({ status: "error", @@ -93,6 +84,7 @@ describe("Create a new document", () => { testCollection, testConverter, modelToSave, + defaultLanguage, ); expect(result).toMatchObject({ status: "error", @@ -102,4 +94,24 @@ describe("Create a new document", () => { }, }); }); + + test("Should return an error if passed language is not default one", async () => { + const modelToSave: TestModel = { + lastUpdate: new Date("2024-01-01"), + name: "Test Document", + }; + const result = await createDocument( + testCollection, + testConverter, + modelToSave, + "it", + ); + expect(result).toMatchObject({ + status: "error", + data: { + code: `${testCollection}/create-error:default-language-is-required`, + message: "Default language is required", + }, + }); + }); }); diff --git a/src/data/firestore/common/create.ts b/src/data/firestore/common/create.ts index 81a98bc2..6c277d63 100644 --- a/src/data/firestore/common/create.ts +++ b/src/data/firestore/common/create.ts @@ -1,12 +1,10 @@ -import type { - DocumentData, - FirestoreDataConverter, -} from "firebase-admin/firestore"; import { firestoreInstance } from "~/firebase/server"; import type { BaseModel } from "~/models/base-model"; import type { ServerResponse } from "~/models/server-response/server-response.type"; +import { defaultLanguage, type SupportedLanguages } from "~/constants/i18n"; +import type {DocumentData, FirestoreDataConverter} from "firebase-admin/firestore"; -export const createDocument = async < +const createDocument = async < M extends BaseModel, D extends DocumentData, C extends string, @@ -14,13 +12,25 @@ export const createDocument = async < collection: C, converter: FirestoreDataConverter, model: M, + currentLanguage: SupportedLanguages, ): Promise< ServerResponse< string, - `${C}/create-error` | `${C}/create-error:id-is-present` + | `${C}/create-error` + | `${C}/create-error:id-is-present` + | `${C}/create-error:default-language-is-required` > > => { try { + if (currentLanguage !== defaultLanguage) { + return { + status: "error", + data: { + code: `${collection}/create-error:default-language-is-required`, + message: "Default language is required", + }, + }; + } if (model.id) { return { status: "error", @@ -49,3 +59,5 @@ export const createDocument = async < }; } }; + +export default createDocument; diff --git a/src/data/firestore/common/delete.spec.ts b/src/data/firestore/common/delete.spec.ts index 1babd5fd..ac8157de 100644 --- a/src/data/firestore/common/delete.spec.ts +++ b/src/data/firestore/common/delete.spec.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from "vitest"; import { deleteDocument } from "./delete"; -import { createDocument } from "./create"; -import { getDocumentById } from "./get-by-id"; import { testConverter, type TestModel } from "./test-model"; +import createDocument from "./create"; +import { defaultLanguage } from "~/constants/i18n"; const testCollection = "delete-test"; @@ -17,22 +17,9 @@ describe("deleteDocument", () => { testCollection, testConverter, modelToSave, + defaultLanguage, ); - const get = await getDocumentById( - testCollection, - testConverter, - result.data as string, - ); - - expect(get).toMatchObject({ - status: "success", - data: expect.objectContaining({ - ...modelToSave, - id: result.data, - }), - }); - const deleteResult = await deleteDocument( testCollection, result.data as string, @@ -42,22 +29,6 @@ describe("deleteDocument", () => { status: "success", data: null, }); - - const getAfterDelete = await getDocumentById( - testCollection, - testConverter, - result.data as string, - ); - - expect(getAfterDelete).toMatchObject({ - status: "error", - data: { - code: `${testCollection}/get-by-id-error:not-found`, - message: `Document with id ${ - result.data as string - } not found in collection delete-test`, - }, - }); }); test("should return an error if id is missing", async () => { diff --git a/src/data/firestore/common/get-by-id.spec.ts b/src/data/firestore/common/get-by-id.spec.ts index 5d7477db..297af498 100644 --- a/src/data/firestore/common/get-by-id.spec.ts +++ b/src/data/firestore/common/get-by-id.spec.ts @@ -1,8 +1,9 @@ import { describe, expect, test } from "vitest"; -import { createDocument } from "./create"; import { getDocumentById } from "./get-by-id"; import { testConverter, type TestDoc, type TestModel } from "./test-model"; import type { FirestoreDataConverter } from "firebase-admin/firestore"; +import createDocument from "./create"; +import { defaultLanguage } from "~/constants/i18n"; const testCollection = "get-by-id-test"; @@ -17,6 +18,7 @@ describe("Get by id", () => { testCollection, testConverter, modelToSave, + defaultLanguage, ); const get = await getDocumentById( @@ -84,6 +86,7 @@ describe("Get by id", () => { testCollection, testConverter, modelToSave, + defaultLanguage, ); const fakeConverter: FirestoreDataConverter = { diff --git a/src/data/firestore/common/test-model.ts b/src/data/firestore/common/test-model.ts index 662c2481..81779c44 100644 --- a/src/data/firestore/common/test-model.ts +++ b/src/data/firestore/common/test-model.ts @@ -1,31 +1,37 @@ +import type {LocalizedFirestoreDocument} from "~/data/firestore/docs/model-document-mapper"; +import {type FirestoreDataConverter, type QueryDocumentSnapshot, Timestamp} from "firebase-admin/firestore"; import { - QueryDocumentSnapshot, - Timestamp, - type FirestoreDataConverter, -} from "firebase-admin/firestore"; -import type { FirestoreDocument } from "~/data/model-document-mapper"; + localizedFieldFromFirestore, + localizedFieldToFirestore +} from "~/data/firestore/docs/converters/localization-converter.ts"; +import omit from "~/data/firestore/docs/utils.ts"; export type TestModel = { id?: string; name: string; + description?: string; lastUpdate: Date; }; -export type TestDoc = FirestoreDocument; +type TestDocLocalizedFields = "description"; + +export type TestDoc = LocalizedFirestoreDocument export const testConverter: FirestoreDataConverter = { - toFirestore: (company: TestModel): TestDoc => { - return { - ...company, - lastUpdate: Timestamp.fromDate(company.lastUpdate), + toFirestore: (model: TestModel): TestDoc => { + const rest = omit(model, ["id", "description"]); + const doc: TestDoc = { + ...rest, + lastUpdate: Timestamp.fromDate(model.lastUpdate), }; + return localizedFieldToFirestore(model, "en", ["description"], doc); }, fromFirestore: (snapshot: QueryDocumentSnapshot): TestModel => { const data = snapshot.data(); + const model = localizedFieldFromFirestore(snapshot, "en", ["description"]); return { - id: snapshot.id, - ...data, + ...model, lastUpdate: data.lastUpdate.toDate(), - } as TestModel; + }; }, -}; +}; \ No newline at end of file diff --git a/src/data/firestore/common/update.spec.ts b/src/data/firestore/common/update.spec.ts index 0b80c6ad..c5e9a2dd 100644 --- a/src/data/firestore/common/update.spec.ts +++ b/src/data/firestore/common/update.spec.ts @@ -1,9 +1,10 @@ import { test, describe, expect } from "vitest"; import { updateDocument } from "./update"; import { getDocumentById } from "./get-by-id"; -import { createDocument } from "./create"; import { testConverter, type TestDoc, type TestModel } from "./test-model"; import type { FirestoreDataConverter } from "firebase-admin/firestore"; +import createDocument from "./create"; +import { defaultLanguage } from "~/constants/i18n"; const testCollection = "update-test"; @@ -18,6 +19,7 @@ describe("Update a new document", () => { testCollection, testConverter, modelToSave, + defaultLanguage, ); const modelToUpdate = { @@ -88,6 +90,7 @@ describe("Update a new document", () => { testCollection, testConverter, modelToSave, + defaultLanguage, ); const modelToUpdate = { diff --git a/src/data/firestore/docs/converters/localization-converter.spec.ts b/src/data/firestore/docs/converters/localization-converter.spec.ts new file mode 100644 index 00000000..f8a988f1 --- /dev/null +++ b/src/data/firestore/docs/converters/localization-converter.spec.ts @@ -0,0 +1,252 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { defaultLanguage, type SupportedLanguages } from "~/constants/i18n"; +import type { BaseModel } from "~/models/base-model"; +import type { LocalizedFirestoreDocument } from "~/data/firestore/docs/model-document-mapper"; +import type { + DocumentData, + QueryDocumentSnapshot, +} from "firebase-admin/firestore"; + +import type { LocalizedField } from "~/data/firestore/docs/localized-field"; +import { + localizedFieldFromFirestore, + localizedFieldToFirestore, +} from "~/data/firestore/docs/converters/localization-converter.ts"; + +type TestModel = BaseModel & { + title: string; + description: string; + subtitle?: string; +}; + +type TestLocalizedFields = "title" | "subtitle"; + +type TestDocument = LocalizedFirestoreDocument; + +function createSimpleMockQueryDocumentSnapshot( + id: string, + data: T, +): QueryDocumentSnapshot { + return { + id, + data: vi.fn(() => data), + ref: { id } as any, + metadata: { hasPendingWrites: false, fromCache: false }, + } as unknown as QueryDocumentSnapshot; +} + +describe("localizedFieldToFirestore", () => { + let model: TestModel; + let currentDocument: TestDocument; + const localizedFields: TestLocalizedFields[] = ["title"]; + + beforeEach(() => { + model = { + id: "123", + title: "New Title", + description: "New Description", + }; + currentDocument = { + id: "123", + title: { en: "Old Title", it: "Titolo Vecchio" } as LocalizedField, + description: "Old Description", + }; + }); + + it("should correctly convert a model to a Firestore document", () => { + const result = localizedFieldToFirestore( + model, + "en", + localizedFields, + currentDocument, + ); + expect(result).toEqual({ + id: "123", + title: { en: "New Title", it: "Titolo Vecchio" }, + description: "New Description", + }); + }); + + it("should add a new language if it does not exist in the current document", () => { + const result = localizedFieldToFirestore( + model, + "fr" as SupportedLanguages, + localizedFields, + currentDocument, + ); + expect(result.title as LocalizedField).toHaveProperty("fr", "New Title"); + }); + + it("should throw an error when trying to set default language to empty string", () => { + model.title = ""; + expect(() => + localizedFieldToFirestore( + model, + defaultLanguage, + localizedFields, + currentDocument, + ), + ).toThrow( + `Cannot set default language (${defaultLanguage}) to an empty string for field title`, + ); + }); + + it("should throw an error when a localized field in the model is not a string", () => { + (model as any).title = 42; + expect(() => + localizedFieldToFirestore(model, "en", localizedFields, currentDocument), + ).toThrow("Field title must be a string in the model"); + }); + + it("should handle multiple localized fields", () => { + const multiModel: TestModel = { ...model, subtitle: "New Subtitle" }; + const multiDocument: TestDocument = { + ...currentDocument, + subtitle: { + en: "Old Subtitle", + it: "Sottotitolo Vecchio", + } as LocalizedField, + }; + const multiLocalizedFields: TestLocalizedFields[] = ["title", "subtitle"]; + + const result = localizedFieldToFirestore( + multiModel, + "en", + multiLocalizedFields, + multiDocument, + ); + expect(result).toEqual({ + id: "123", + title: { en: "New Title", it: "Titolo Vecchio" }, + subtitle: { en: "New Subtitle", it: "Sottotitolo Vecchio" }, + description: "New Description", + }); + }); + + it("should not modify non-localized fields", () => { + const result = localizedFieldToFirestore( + model, + "en", + localizedFields, + currentDocument, + ); + expect(result.description).toBe("New Description"); + }); +}); + +describe("localizedFieldFromFirestore", () => { + const localizedFields: TestLocalizedFields[] = ["title"]; + + it("should correctly convert a Firestore document to a model", async () => { + const firestoreData = { + id: "123", + title: { en: "English Title", it: "Titolo Italiano" } as LocalizedField, + description: "Some Description", + }; + + const mockSnapshot = createSimpleMockQueryDocumentSnapshot( + "123", + firestoreData, + ); + + const result = localizedFieldFromFirestore( + mockSnapshot, + "en", + localizedFields, + ); + expect(result).toEqual({ + id: "123", + title: "English Title", + description: "Some Description", + }); + }); + + it("should use the current language value if available", () => { + const firestoreData = { + id: "123", + title: { en: "English Title", it: "Titolo Italiano" } as LocalizedField, + description: "Some Description", + }; + const mockSnapshot = createSimpleMockQueryDocumentSnapshot( + "123", + firestoreData, + ); + + const result = localizedFieldFromFirestore( + mockSnapshot, + "it", + localizedFields, + ); + expect(result.title).toBe("Titolo Italiano"); + }); + + it("should handle non-localized fields correctly", () => { + const firestoreData = { + id: "123", + title: { en: "English Title", it: "Titolo Italiano" } as LocalizedField, + description: "Some Description", + }; + const mockSnapshot = createSimpleMockQueryDocumentSnapshot( + "123", + firestoreData, + ); + + const result = localizedFieldFromFirestore( + mockSnapshot, + "en", + localizedFields, + ); + expect(result.description).toBe("Some Description"); + }); + + it("should return undefined for a language that does not exist in the localized field", () => { + const firestoreData = { + id: "123", + title: { en: "English Title" } as LocalizedField, + description: "Some Description", + }; + const mockSnapshot = createSimpleMockQueryDocumentSnapshot( + "123", + firestoreData, + ); + + const result = localizedFieldFromFirestore( + mockSnapshot, + "it" as SupportedLanguages, + localizedFields, + ); + expect(result.title).toBeUndefined(); + }); + + it("should handle multiple localized fields", () => { + const firestoreData = { + id: "123", + title: { en: "English Title", it: "Titolo Italiano" } as LocalizedField, + description: "Some Description", + }; + const multiData: TestDocument = { + ...firestoreData, + subtitle: { + en: "English Subtitle", + it: "Sottotitolo Italiano", + } as LocalizedField, + }; + const mockSnapshot = createSimpleMockQueryDocumentSnapshot( + "123", + multiData, + ); + const multiLocalizedFields: TestLocalizedFields[] = ["title", "subtitle"]; + + const result = localizedFieldFromFirestore( + mockSnapshot, + "it", + multiLocalizedFields, + ); + expect(result).toEqual({ + id: "123", + title: "Titolo Italiano", + subtitle: "Sottotitolo Italiano", + description: "Some Description", + }); + }); +}); diff --git a/src/data/firestore/docs/converters/localization-converter.ts b/src/data/firestore/docs/converters/localization-converter.ts new file mode 100644 index 00000000..b3aa841e --- /dev/null +++ b/src/data/firestore/docs/converters/localization-converter.ts @@ -0,0 +1,73 @@ +import { defaultLanguage, type SupportedLanguages } from "~/constants/i18n"; +import type { QueryDocumentSnapshot } from "firebase-admin/firestore"; +import type { BaseModel } from "~/models/base-model"; +import type { LocalizedFirestoreDocument } from "~/data/firestore/docs/model-document-mapper.ts"; +import { + isLocalizedField, + type LocalizedField, +} from "~/data/firestore/docs/localized-field.ts"; + +const localizedFieldToFirestore = < + M extends BaseModel, + K extends keyof M, + D extends LocalizedFirestoreDocument, +>( + model: M, + currentLanguage: SupportedLanguages, + localizedFields: K[], + currentDocument: D, +): LocalizedFirestoreDocument => { + const result = { ...model } as unknown as D; + + localizedFields.forEach((field) => { + let localizedValue = { + ...(currentDocument[field] as LocalizedField), + }; + const modelValue = model[field]; + if (!modelValue && currentLanguage === defaultLanguage) { + throw new Error( + `Cannot set default language (${defaultLanguage}) to an empty string for field ${String(field)}`, + ); + } + + if (modelValue) { + if (typeof modelValue !== "string") { + throw new Error(`Field ${String(field)} must be a string in the model`); + } + + if (currentLanguage === defaultLanguage && modelValue.trim() === "") { + throw new Error( + `Cannot set default language (${defaultLanguage}) to an empty string for field ${String(field)}`, + ); + } + + localizedValue[currentLanguage] = modelValue; + } + + result[field] = localizedValue as any; + }); + + return result; +}; + +const localizedFieldFromFirestore = ( + snapshot: QueryDocumentSnapshot, + currentLanguage: SupportedLanguages, + localizedFields: ReadonlyArray, +): M => { + const data = snapshot.data() as LocalizedFirestoreDocument; + const result = { ...data, id: snapshot.id } as M; + + localizedFields.forEach((field) => { + const value = data[field]; + if (isLocalizedField(value)) { + result[field] = value[currentLanguage] as M[K]; + } else { + result[field] = value as M[K]; + } + }); + + return result; +}; + +export { localizedFieldToFirestore, localizedFieldFromFirestore }; diff --git a/src/data/firestore/docs/localized-field.ts b/src/data/firestore/docs/localized-field.ts new file mode 100644 index 00000000..2c97fcbc --- /dev/null +++ b/src/data/firestore/docs/localized-field.ts @@ -0,0 +1,14 @@ +import { defaultLanguage, type supportedLanguages } from "~/constants/i18n"; + +type SupportedLanguage = typeof supportedLanguages[number]; + +type LocalizedField = Partial>; + +const isLocalizedField = (value: unknown): value is LocalizedField => { + return ( + typeof value === "object" && value !== null && defaultLanguage in value + ); +}; + +export type { LocalizedField }; +export { isLocalizedField }; diff --git a/src/data/firestore/docs/model-document-mapper.ts b/src/data/firestore/docs/model-document-mapper.ts new file mode 100644 index 00000000..97f4aae3 --- /dev/null +++ b/src/data/firestore/docs/model-document-mapper.ts @@ -0,0 +1,24 @@ +import { Timestamp } from "firebase-admin/firestore"; +import type { LocalizedField } from "./localized-field"; + +type MapDateToTimestamp = PropType extends Date + ? Timestamp + : PropType; + +type OmitDate = { + [K in keyof T as T[K] extends Date ? never : K]: T[K] +}; + +type FirestoreDocument = Omit, "id"> & { + [PropertyKey in keyof T]: MapDateToTimestamp; +}; + + +type LocalizedFirestoreDocument = Omit< + FirestoreDocument, + K +> & { + [P in keyof T]: P extends K ? LocalizedField : MapDateToTimestamp; +}; + +export type { FirestoreDocument, LocalizedFirestoreDocument }; diff --git a/src/data/firestore/docs/utils.ts b/src/data/firestore/docs/utils.ts new file mode 100644 index 00000000..7ddd7a74 --- /dev/null +++ b/src/data/firestore/docs/utils.ts @@ -0,0 +1,7 @@ +const omit = (obj: T, keys: K[]): Omit => { + const result = { ...obj }; + keys.forEach(key => delete result[key]); + return result; +} + +export default omit; \ No newline at end of file diff --git a/src/data/firestore/user.spec.ts b/src/data/firestore/user.spec.ts index 85217d9a..fb73d11a 100644 --- a/src/data/firestore/user.spec.ts +++ b/src/data/firestore/user.spec.ts @@ -15,7 +15,6 @@ describe("Get docs paginated", () => { { displayName: "Name 1", email: "email@email.it", - organizer: true, }, { displayName: "Name 2", @@ -24,7 +23,9 @@ describe("Get docs paginated", () => { { displayName: "Name 3", email: "email3@email.it", - organizer: true, + customClaims: { + organizer: true, + }, }, { displayName: "Name 4", diff --git a/src/data/firestore/user.ts b/src/data/firestore/user.ts index 19d8be06..5255fc1d 100644 --- a/src/data/firestore/user.ts +++ b/src/data/firestore/user.ts @@ -1,11 +1,11 @@ import type { User } from "~/models/user/user.type"; -import type { FirestoreDocument } from "../model-document-mapper"; import type { FirestoreDataConverter, QueryDocumentSnapshot, } from "firebase-admin/firestore"; import type { PaginationParams } from "~/models/pagination/pagination.type"; import getDocsPaginated from "./common/get-docs-paginated"; +import type { FirestoreDocument } from "~/data/firestore/docs/model-document-mapper.ts"; const collection = "users" as const; diff --git a/src/data/model-document-mapper.ts b/src/data/model-document-mapper.ts deleted file mode 100644 index ec863216..00000000 --- a/src/data/model-document-mapper.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Timestamp } from "firebase-admin/firestore"; - -type MapDateToTimestamp = PropType extends Date - ? Timestamp - : PropType; - -export type FirestoreDocument = { - [PropertyKey in keyof T]: MapDateToTimestamp; -}; diff --git a/src/models/localization/localization.schema.ts b/src/models/localization/localization.schema.ts new file mode 100644 index 00000000..49e0d69f --- /dev/null +++ b/src/models/localization/localization.schema.ts @@ -0,0 +1,8 @@ +import { z } from "astro/zod"; +import { supportedLanguages } from "~/constants/i18n"; + +const localizationParamSchema = z.object({ + language: z.enum(supportedLanguages), +}); + +export { localizationParamSchema }; diff --git a/src/models/localization/localization.type.ts b/src/models/localization/localization.type.ts new file mode 100644 index 00000000..ff808193 --- /dev/null +++ b/src/models/localization/localization.type.ts @@ -0,0 +1,7 @@ +import type { SupportedLanguages } from "~/constants/i18n"; + +type LocalizationParam = { + language?: SupportedLanguages; +}; + +export type { LocalizationParam }; diff --git a/src/models/pagination/pagination.type.ts b/src/models/pagination/pagination.type.ts index 24748a1a..e68ab3a0 100644 --- a/src/models/pagination/pagination.type.ts +++ b/src/models/pagination/pagination.type.ts @@ -1,4 +1,5 @@ import type { GetAllDocumentsParam } from "~/models/get-all/get-all-document.type.ts"; +import type {LocalizationParam} from "~/models/localization/localization.type.ts"; type PaginatedResponse = { data: T[]; @@ -8,7 +9,7 @@ type PaginatedResponse = { totalPages: number; }; -type PaginationParams = GetAllDocumentsParam & { +type PaginationParams = GetAllDocumentsParam & LocalizationParam & { offset: number; limit: number; }; diff --git a/src/models/user/user.type.ts b/src/models/user/user.type.ts index d8c86180..8b659e1b 100644 --- a/src/models/user/user.type.ts +++ b/src/models/user/user.type.ts @@ -3,7 +3,7 @@ import type { BaseModel } from "../base-model"; type User = BaseModel & { displayName: string; email: string; - customClaims: CustomClaims; + customClaims?: CustomClaims; }; type CustomClaims = { diff --git a/src/shared/Dummy/dummy.test.ts b/src/shared/Dummy/dummy.test.ts deleted file mode 100644 index e7fa3f45..00000000 --- a/src/shared/Dummy/dummy.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { expect, it } from "vitest"; - -it("should pass", () => { - expect(true).toBe(true); -});