Finish switch from Jest to Vitest in API (#16263)

* build:  add vitest and update test scripts

* build: 🔧 add vitest config

* build:  Migrate tests to vitest

Remove jest references from test api test files and replace with vitest equivalents.

Tests: 13 tests are failing.

* build: 🚚 move vite.config.ts to api/src folder

* build: 🔥 remove unused vitest.config from api root

* build:  import vitest modules for tests

* build:  add type conversion for actual object

* Finish switch from Jest to Vitest in API

* Replace some leftovers

* Load "sharp" before tests

* Try with cjs

* Temporary enable verbose reporter

* Try with globalSetup

* Fix path to globalSetup

* Provide default export in globalSetup

* Final clean-up

* Remove @vitest/ui & update vitest to 0.25.0

* Add vitest c8 coverage dependency

* Update vitest to v0.25.1

* Replace unnecessary Vitest workaround

* Rework new tests

* Resolve build errors

Co-authored-by: Dorian C Brown <brown.3794@gmail.com>
Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
This commit is contained in:
Pascal Jufer
2022-11-14 22:09:47 +01:00
committed by GitHub
parent 20b8146221
commit c303bdcf10
40 changed files with 416 additions and 392 deletions

7
api/globalSetup.js Normal file
View File

@@ -0,0 +1,7 @@
// From https://sharp.pixelplumbing.com/install#worker-threads:
// On some platforms, including glibc-based Linux, the main thread must call require('sharp') before worker threads are created.
// This is to ensure shared libraries remain loaded in memory until after all threads are complete.
// Without this, the following error may occur: Module did not self-register
import 'sharp';
export default function () {}

View File

@@ -1,14 +0,0 @@
const base = require('../jest.config.js');
require('dotenv').config();
module.exports = {
...base,
roots: ['<rootDir>/src'],
verbose: true,
setupFiles: ['dotenv/config'],
collectCoverageFrom: ['src/**/*.ts'],
testEnvironmentOptions: {
url: process.env.TEST_URL || 'http://localhost',
},
};

View File

@@ -63,9 +63,9 @@
"cleanup": "rimraf dist",
"dev": "cross-env NODE_ENV=development SERVE_APP=false ts-node-dev --files --transpile-only --respawn --watch \".env\" --inspect=0 --exit-child -- src/start.ts",
"cli": "cross-env NODE_ENV=development SERVE_APP=false ts-node --script-mode --transpile-only src/cli/run.ts",
"test": "jest",
"test:coverage": "jest --coverage",
"test:watch": "jest --watch"
"test": "vitest run",
"test:coverage": "vitest run --coverage",
"test:watch": "vitest"
},
"engines": {
"node": ">=12.20.0"
@@ -197,7 +197,6 @@
"@types/flat": "5.0.2",
"@types/fs-extra": "9.0.13",
"@types/inquirer": "8.2.1",
"@types/jest": "29.2.0",
"@types/js-yaml": "4.0.5",
"@types/json2csv": "5.0.3",
"@types/jsonwebtoken": "8.5.9",
@@ -221,16 +220,16 @@
"@types/uuid": "8.3.4",
"@types/uuid-validate": "0.0.1",
"@types/wellknown": "0.5.3",
"@vitest/coverage-c8": "^0.25.1",
"copyfiles": "2.4.1",
"cross-env": "7.0.3",
"form-data": "4.0.0",
"jest": "29.2.1",
"knex-mock-client": "1.8.4",
"rimraf": "3.0.2",
"supertest": "6.3.0",
"ts-jest": "29.0.3",
"ts-node": "10.9.1",
"ts-node-dev": "2.0.0",
"typescript": "4.8.4"
"typescript": "4.8.4",
"vitest": "0.25.1"
}
}

View File

@@ -1,4 +1,5 @@
export const getCache = jest.fn().mockReturnValue({ cache: undefined, systemCache: undefined, lockCache: undefined });
export const flushCaches = jest.fn();
export const clearSystemCache = jest.fn();
export const setSystemCache = jest.fn();
import { vi } from 'vitest';
export const getCache = vi.fn().mockReturnValue({ cache: undefined, systemCache: undefined, lockCache: undefined });
export const flushCaches = vi.fn();
export const clearSystemCache = vi.fn();
export const setSystemCache = vi.fn();

View File

@@ -1,39 +1,48 @@
import path from 'path';
import { Command } from 'commander';
import { Extension, HookConfig } from '@directus/shared/types';
import { createCli } from './index';
import path from 'path';
import { test, describe, expect, vi, beforeEach } from 'vitest';
jest.mock('../../src/env', () => ({
...jest.requireActual('../../src/env').default,
EXTENSIONS_PATH: '',
SERVE_APP: false,
DB_CLIENT: 'pg',
DB_HOST: 'localhost',
DB_PORT: 5432,
DB_DATABASE: 'directus',
DB_USER: 'postgres',
DB_PASSWORD: 'psql1234',
}));
vi.mock('../../src/env', async () => {
const actual = (await vi.importActual('../../src/env')) as { default: Record<string, any> };
jest.mock('@directus/shared/utils/node/get-extensions', () => ({
getPackageExtensions: jest.fn(() => Promise.resolve([])),
getLocalExtensions: jest.fn(() => Promise.resolve([customCliExtension])),
}));
return {
default: {
...actual.default,
EXTENSIONS_PATH: '',
SERVE_APP: false,
DB_CLIENT: 'pg',
DB_HOST: 'localhost',
DB_PORT: 5432,
DB_DATABASE: 'directus',
DB_USER: 'postgres',
DB_PASSWORD: 'psql1234',
},
};
});
const customHookPath = path.resolve('/hooks/custom-cli', 'index.js');
jest.doMock(customHookPath, () => customCliHook, { virtual: true });
vi.mock('@directus/shared/utils/node', async () => {
const actual = await vi.importActual('@directus/shared/utils/node');
const customCliExtension: Extension = {
path: `/hooks/custom-cli`,
name: 'custom-cli',
type: 'hook',
entrypoint: 'index.js',
local: true,
};
const customCliExtension: Extension = {
path: '/hooks/custom-cli',
name: 'custom-cli',
type: 'hook',
entrypoint: 'index.js',
local: true,
};
const beforeHook = jest.fn();
const afterAction = jest.fn();
const afterHook = jest.fn(({ program }) => {
return {
...(actual as object),
getPackageExtensions: vi.fn(() => Promise.resolve([])),
getLocalExtensions: vi.fn(() => Promise.resolve([customCliExtension])),
};
});
const beforeHook = vi.fn();
const afterAction = vi.fn();
const afterHook = vi.fn(({ program }) => {
(program as Command).command('custom').action(afterAction);
});
@@ -42,8 +51,12 @@ const customCliHook: HookConfig = ({ init }) => {
init('cli.after', afterHook);
};
const writeOut = jest.fn();
const writeErr = jest.fn();
vi.mock(path.resolve('/hooks/custom-cli', 'index.js'), () => ({
default: customCliHook,
}));
const writeOut = vi.fn();
const writeErr = vi.fn();
const setup = async () => {
const program = await createCli();
@@ -52,7 +65,9 @@ const setup = async () => {
return program;
};
beforeEach(jest.clearAllMocks);
beforeEach(() => {
vi.clearAllMocks();
});
describe('cli hooks', () => {
test('should call hooks before and after creating the cli', async () => {

View File

@@ -1,14 +1,14 @@
// @ts-nocheck
jest.mock('../../src/cache');
jest.mock('../../src/database');
jest.mock('../../src/utils/validate-env');
import { multipartHandler } from './files';
import { InvalidPayloadException } from '../exceptions/invalid-payload';
import { PassThrough } from 'stream';
import FormData from 'form-data';
import { vi, describe, expect, it } from 'vitest';
vi.mock('../../src/cache');
vi.mock('../../src/database');
vi.mock('../../src/utils/validate-env');
describe('multipartHandler', () => {
it(`Errors out if request doesn't contain any files to upload`, () => {
@@ -18,7 +18,7 @@ describe('multipartHandler', () => {
const req = {
headers: fakeForm.getHeaders(),
is: jest.fn().mockReturnValue(true),
is: vi.fn().mockReturnValue(true),
body: fakeForm.getBuffer(),
params: {},
pipe: (input) => stream.pipe(input),
@@ -45,7 +45,7 @@ describe('multipartHandler', () => {
const req = {
headers: fakeForm.getHeaders(),
is: jest.fn().mockReturnValue(true),
is: vi.fn().mockReturnValue(true),
body: fakeForm.getBuffer(),
params: {},
pipe: (input) => stream.pipe(input),

View File

@@ -1,13 +1,14 @@
import knex, { Knex } from 'knex';
import { getTracker, MockClient, Tracker } from 'knex-mock-client';
import run from './run';
import { describe, beforeAll, afterEach, it, expect } from 'vitest';
describe('run', () => {
let db: jest.Mocked<Knex>;
let db: Knex;
let tracker: Tracker;
beforeAll(() => {
db = knex({ client: MockClient }) as jest.Mocked<Knex>;
db = knex({ client: MockClient });
tracker = getTracker();
});

View File

@@ -63,7 +63,7 @@ export default async function run(database: Knex, direction: 'up' | 'down' | 'la
throw Error('Nothing to upgrade');
}
const { up } = require(nextVersion.file);
const { up } = await import(nextVersion.file);
if (log) {
logger.info(`Applying ${nextVersion.name}...`);
@@ -86,7 +86,7 @@ export default async function run(database: Knex, direction: 'up' | 'down' | 'la
throw new Error("Couldn't find migration");
}
const { down } = require(migration.file);
const { down } = await import(migration.file);
if (log) {
logger.info(`Undoing ${migration.name}...`);
@@ -99,7 +99,7 @@ export default async function run(database: Knex, direction: 'up' | 'down' | 'la
async function latest() {
for (const migration of migrations) {
if (migration.completed === false) {
const { up } = require(migration.file);
const { up } = await import(migration.file);
if (log) {
logger.info(`Applying ${migration.name}...`);

View File

@@ -1,3 +1,5 @@
import { describe, test, expect, vi } from 'vitest';
const testEnv = {
NUMBER: '1234',
NUMBER_CAST_AS_STRING: 'string:1234',
@@ -7,20 +9,9 @@ const testEnv = {
MULTIPLE: 'array:string:https://example.com,regex:\\.example2\\.com$',
};
describe('env processed values', () => {
const originalEnv = process.env;
let env: Record<string, any>;
beforeEach(() => {
jest.resetModules();
process.env = { ...testEnv };
env = jest.requireActual('../src/env').default;
});
afterEach(() => {
process.env = originalEnv;
jest.resetAllMocks();
});
describe('env processed values', async () => {
process.env = { ...testEnv };
const env = ((await vi.importActual('../src/env')) as { default: Record<string, any> }).default;
test('Number value should be a number', () => {
expect(env.NUMBER).toStrictEqual(1234);

View File

@@ -197,7 +197,7 @@ class ExtensionManager {
logger.warn(err);
}
this.registerHooks();
await this.registerHooks();
this.registerEndpoints();
await this.registerOperations();
@@ -341,13 +341,12 @@ class ExtensionManager {
return depsMapping;
}
private registerHooks(): void {
private async registerHooks(): Promise<void> {
const hooks = this.extensions.filter((extension): extension is ApiExtension => extension.type === 'hook');
for (const hook of hooks) {
try {
const hookPath = path.resolve(hook.path, hook.entrypoint);
const hookInstance: HookConfig | { default: HookConfig } = require(hookPath);
const hookInstance: HookConfig | { default: HookConfig } = await import(hookPath);
const config = getModuleDefault(hookInstance);

View File

@@ -7,28 +7,31 @@ import env from '../env';
import { InvalidCredentialsException } from '../exceptions';
import { handler } from './authenticate';
import '../../src/types/express.d.ts';
import { vi, afterEach, test, expect } from 'vitest';
jest.mock('../../src/database');
jest.mock('../../src/env', () => ({
SECRET: 'test',
vi.mock('../../src/database');
vi.mock('../../src/env', () => ({
default: {
SECRET: 'test',
},
}));
afterEach(() => {
jest.resetAllMocks();
vi.resetAllMocks();
});
test('Short-circuits when authenticate filter is used', async () => {
const req = {
ip: '127.0.0.1',
get: jest.fn(),
get: vi.fn(),
};
const res = {};
const next = jest.fn();
const next = vi.fn();
const customAccountability = { admin: true };
jest.spyOn(emitter, 'emitFilter').mockResolvedValue(customAccountability);
vi.spyOn(emitter, 'emitFilter').mockResolvedValue(customAccountability);
await handler(req, res, next);
@@ -39,7 +42,7 @@ test('Short-circuits when authenticate filter is used', async () => {
test('Uses default public accountability when no token is given', async () => {
const req = {
ip: '127.0.0.1',
get: jest.fn((string) => {
get: vi.fn((string) => {
switch (string) {
case 'user-agent':
return 'fake-user-agent';
@@ -52,9 +55,9 @@ test('Uses default public accountability when no token is given', async () => {
};
const res = {};
const next = jest.fn();
const next = vi.fn();
jest.spyOn(emitter, 'emitFilter').mockImplementation((_, payload) => payload);
vi.spyOn(emitter, 'emitFilter').mockImplementation((_, payload) => payload);
await handler(req, res, next);
@@ -97,7 +100,7 @@ test('Sets accountability to payload contents if valid token is passed', async (
const req = {
ip: '127.0.0.1',
get: jest.fn((string) => {
get: vi.fn((string) => {
switch (string) {
case 'user-agent':
return 'fake-user-agent';
@@ -111,7 +114,7 @@ test('Sets accountability to payload contents if valid token is passed', async (
};
const res = {};
const next = jest.fn();
const next = vi.fn();
await handler(req, res, next);
@@ -163,17 +166,17 @@ test('Sets accountability to payload contents if valid token is passed', async (
});
test('Throws InvalidCredentialsException when static token is used, but user does not exist', async () => {
jest.mocked(getDatabase).mockReturnValue({
select: jest.fn().mockReturnThis(),
from: jest.fn().mockReturnThis(),
leftJoin: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
first: jest.fn().mockResolvedValue(undefined),
vi.mocked(getDatabase).mockReturnValue({
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
leftJoin: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnThis(),
first: vi.fn().mockResolvedValue(undefined),
});
const req = {
ip: '127.0.0.1',
get: jest.fn((string) => {
get: vi.fn((string) => {
switch (string) {
case 'user-agent':
return 'fake-user-agent';
@@ -187,7 +190,7 @@ test('Throws InvalidCredentialsException when static token is used, but user doe
};
const res = {};
const next = jest.fn();
const next = vi.fn();
expect(handler(req, res, next)).rejects.toEqual(new InvalidCredentialsException());
expect(next).toHaveBeenCalledTimes(0);
@@ -196,7 +199,7 @@ test('Throws InvalidCredentialsException when static token is used, but user doe
test('Sets accountability to user information when static token is used', async () => {
const req = {
ip: '127.0.0.1',
get: jest.fn((string) => {
get: vi.fn((string) => {
switch (string) {
case 'user-agent':
return 'fake-user-agent';
@@ -210,7 +213,7 @@ test('Sets accountability to user information when static token is used', async
};
const res = {};
const next = jest.fn();
const next = vi.fn();
const testUser = { id: 'test-id', role: 'test-role', admin_access: true, app_access: false };
@@ -224,12 +227,12 @@ test('Sets accountability to user information when static token is used', async
origin: 'fake-origin',
};
jest.mocked(getDatabase).mockReturnValue({
select: jest.fn().mockReturnThis(),
from: jest.fn().mockReturnThis(),
leftJoin: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
first: jest.fn().mockResolvedValue(testUser),
vi.mocked(getDatabase).mockReturnValue({
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
leftJoin: vi.fn().mockReturnThis(),
where: vi.fn().mockReturnThis(),
first: vi.fn().mockResolvedValue(testUser),
});
await handler(req, res, next);

View File

@@ -1,15 +1,16 @@
import { NextFunction, Request, Response } from 'express';
import { Request, Response } from 'express';
import extractToken from '../../src/middleware/extract-token';
import '../../src/types/express.d.ts';
import { vi, beforeEach, test, expect } from 'vitest';
let mockRequest: Partial<Request & { token?: string }>;
let mockResponse: Partial<Response>;
const nextFunction: NextFunction = jest.fn();
const nextFunction = vi.fn();
beforeEach(() => {
mockRequest = {};
mockResponse = {};
jest.clearAllMocks();
vi.clearAllMocks();
});
test('Token from query', () => {

View File

@@ -1,17 +1,18 @@
import type { NextFunction, Request, Response } from 'express';
import type { Request, Response } from 'express';
import { validateBatch } from './validate-batch';
import '../../src/types/express.d.ts';
import { InvalidPayloadException } from '../exceptions';
import { FailedValidationException } from '@directus/shared/exceptions';
import { vi, beforeEach, test, expect } from 'vitest';
let mockRequest: Partial<Request & { token?: string }>;
let mockResponse: Partial<Response>;
const nextFunction: NextFunction = jest.fn();
const nextFunction = vi.fn();
beforeEach(() => {
mockRequest = {};
mockResponse = {};
jest.clearAllMocks();
vi.clearAllMocks();
});
test('Sets body to empty, calls next on GET requests', async () => {
@@ -39,7 +40,7 @@ test('Throws InvalidPayloadException on missing body', async () => {
await validateBatch('read')(mockRequest as Request, mockResponse as Response, nextFunction);
expect(nextFunction).toHaveBeenCalledTimes(1);
expect(jest.mocked(nextFunction).mock.calls[0][0]).toBeInstanceOf(InvalidPayloadException);
expect(vi.mocked(nextFunction).mock.calls[0][0]).toBeInstanceOf(InvalidPayloadException);
});
test(`Short circuits on Array body in update/delete use`, async () => {
@@ -74,10 +75,10 @@ test(`Doesn't allow both query and keys in a batch delete`, async () => {
query: { filter: {} },
};
await validateBatch('delete')(mockRequest as Request, mockResponse as Response, nextFunction as NextFunction);
await validateBatch('delete')(mockRequest as Request, mockResponse as Response, nextFunction);
expect(nextFunction).toHaveBeenCalledTimes(1);
expect(jest.mocked(nextFunction).mock.calls[0][0]).toBeInstanceOf(FailedValidationException);
expect(vi.mocked(nextFunction).mock.calls[0][0]).toBeInstanceOf(FailedValidationException);
});
test(`Requires 'data' on batch update`, async () => {
@@ -87,10 +88,10 @@ test(`Requires 'data' on batch update`, async () => {
query: { filter: {} },
};
await validateBatch('update')(mockRequest as Request, mockResponse as Response, nextFunction as NextFunction);
await validateBatch('update')(mockRequest as Request, mockResponse as Response, nextFunction);
expect(nextFunction).toHaveBeenCalledTimes(1);
expect(jest.mocked(nextFunction).mock.calls[0][0]).toBeInstanceOf(FailedValidationException);
expect(vi.mocked(nextFunction).mock.calls[0][0]).toBeInstanceOf(FailedValidationException);
});
test(`Calls next when all is well`, async () => {
@@ -101,8 +102,8 @@ test(`Calls next when all is well`, async () => {
data: {},
};
await validateBatch('update')(mockRequest as Request, mockResponse as Response, nextFunction as NextFunction);
await validateBatch('update')(mockRequest as Request, mockResponse as Response, nextFunction);
expect(nextFunction).toHaveBeenCalledTimes(1);
expect(jest.mocked(nextFunction).mock.calls[0][0]).toBeUndefined();
expect(vi.mocked(nextFunction).mock.calls[0][0]).toBeUndefined();
});

View File

@@ -1,4 +1,5 @@
import { VMError } from 'vm2';
import { test, expect } from 'vitest';
import config from './index';
@@ -29,7 +30,7 @@ test('Rejects when code contains syntax errors', async () => {
FLOWS_EXEC_ALLOWED_MODULES: '',
},
} as any)
).rejects.toEqual(new Error("Couldn't compile code: Unexpected end of input"));
).rejects.toEqual(new SyntaxError('Unexpected end of input'));
});
test('Rejects when returned function does something illegal', async () => {

View File

@@ -1,24 +1,25 @@
import { Field } from '@directus/shared/types';
import knex, { Knex } from 'knex';
import { getTracker, MockClient, Tracker } from 'knex-mock-client';
import { afterEach, beforeAll, beforeEach, describe, expect, it, MockedFunction, SpyInstance, vi } from 'vitest';
import { FieldsService } from '.';
import { InvalidPayloadException } from '../exceptions';
jest.mock('../../src/database/index', () => {
vi.mock('../../src/database/index', () => {
return {
__esModule: true,
default: jest.fn(),
getDatabaseClient: jest.fn().mockReturnValue('postgres'),
getSchemaInspector: jest.fn(),
default: vi.fn(),
getDatabaseClient: vi.fn().mockReturnValue('postgres'),
getSchemaInspector: vi.fn(),
};
});
describe('Integration Tests', () => {
let db: jest.Mocked<Knex>;
let db: MockedFunction<Knex>;
let tracker: Tracker;
beforeAll(() => {
db = knex({ client: MockClient }) as jest.Mocked<Knex>;
db = vi.mocked(knex({ client: MockClient }));
tracker = getTracker();
});
@@ -36,11 +37,11 @@ describe('Integration Tests', () => {
});
afterEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
describe('addColumnToTable', () => {
let knexCreateTableBuilderSpy: jest.SpyInstance;
let knexCreateTableBuilderSpy: SpyInstance;
it.each(['alias', 'unknown'])('%s fields should be skipped', async (type) => {
const testCollection = 'test_collection';
@@ -101,7 +102,7 @@ describe('Integration Tests', () => {
tracker.on.any(regex).response({});
await db.schema.alterTable(testCollection, (table) => {
knexCreateTableBuilderSpy = jest.spyOn(table, method as keyof Knex.CreateTableBuilder);
knexCreateTableBuilderSpy = vi.spyOn(table, method as keyof Knex.CreateTableBuilder);
service.addColumnToTable(table, {
collection: testCollection,
@@ -129,7 +130,7 @@ describe('Integration Tests', () => {
tracker.on.any(regex).response({});
await db.schema.alterTable(testCollection, (table) => {
knexCreateTableBuilderSpy = jest.spyOn(table, type as keyof Knex.CreateTableBuilder);
knexCreateTableBuilderSpy = vi.spyOn(table, type as keyof Knex.CreateTableBuilder);
service.addColumnToTable(table, {
collection: testCollection,
@@ -158,7 +159,7 @@ describe('Integration Tests', () => {
tracker.on.any(regex).response({});
await db.schema.alterTable(testCollection, (table) => {
knexCreateTableBuilderSpy = jest.spyOn(table, type as keyof Knex.CreateTableBuilder);
knexCreateTableBuilderSpy = vi.spyOn(table, type as keyof Knex.CreateTableBuilder);
service.addColumnToTable(table, {
collection: testCollection,
@@ -187,7 +188,7 @@ describe('Integration Tests', () => {
tracker.on.any(regex).response({});
await db.schema.alterTable('test_collection', (table) => {
knexCreateTableBuilderSpy = jest.spyOn(table, type as keyof Knex.CreateTableBuilder);
knexCreateTableBuilderSpy = vi.spyOn(table, type as keyof Knex.CreateTableBuilder);
service.addColumnToTable(table, {
collection: testCollection,
@@ -217,7 +218,7 @@ describe('Integration Tests', () => {
tracker.on.any(regex).response({});
await db.schema.alterTable(testCollection, (table) => {
knexCreateTableBuilderSpy = jest.spyOn(table, 'string');
knexCreateTableBuilderSpy = vi.spyOn(table, 'string');
service.addColumnToTable(table, {
collection: testCollection,
@@ -244,7 +245,7 @@ describe('Integration Tests', () => {
tracker.on.any(regex).response({});
await db.schema.alterTable(testCollection, (table) => {
knexCreateTableBuilderSpy = jest.spyOn(table, 'string');
knexCreateTableBuilderSpy = vi.spyOn(table, 'string');
service.addColumnToTable(table, {
collection: testCollection,
@@ -273,7 +274,7 @@ describe('Integration Tests', () => {
tracker.on.any(regex).response({});
await db.schema.alterTable(testCollection, (table) => {
knexCreateTableBuilderSpy = jest.spyOn(table, type as keyof Knex.CreateTableBuilder);
knexCreateTableBuilderSpy = vi.spyOn(table, type as keyof Knex.CreateTableBuilder);
service.addColumnToTable(table, {
collection: testCollection,
@@ -298,7 +299,7 @@ describe('Integration Tests', () => {
const regex = new RegExp(`alter table "${testCollection}" add column "${testField}" .*`);
tracker.on.any(regex).response({});
const thisHelpersStCreateColumnSpy = jest.spyOn(service.helpers.st, 'createColumn');
const thisHelpersStCreateColumnSpy = vi.spyOn(service.helpers.st, 'createColumn');
await db.schema.alterTable(testCollection, (table) => {
service.addColumnToTable(table, {
@@ -334,7 +335,7 @@ describe('Integration Tests', () => {
tracker.on.any(regex).response({});
await db.schema.alterTable(testCollection, (table) => {
knexCreateTableBuilderSpy = jest.spyOn(table, type as keyof Knex.CreateTableBuilder);
knexCreateTableBuilderSpy = vi.spyOn(table, type as keyof Knex.CreateTableBuilder);
service.addColumnToTable(table, {
collection: testCollection,

View File

@@ -3,38 +3,39 @@ import knex, { Knex } from 'knex';
import { MockClient, Tracker, getTracker } from 'knex-mock-client';
import { FilesService, ItemsService } from '.';
import { InvalidPayloadException } from '../exceptions';
import { describe, beforeAll, afterEach, expect, it, vi, beforeEach, SpyInstance } from 'vitest';
jest.mock('exifr');
jest.mock('../../src/database/index', () => {
return { getDatabaseClient: jest.fn().mockReturnValue('postgres') };
vi.mock('exifr');
vi.mock('../../src/database/index', () => {
return { getDatabaseClient: vi.fn().mockReturnValue('postgres') };
});
jest.requireMock('../../src/database/index');
vi.mock('../../src/database/index');
describe('Integration Tests', () => {
let db: jest.Mocked<Knex>;
let db: Knex;
let tracker: Tracker;
beforeAll(async () => {
db = knex({ client: MockClient }) as jest.Mocked<Knex>;
db = knex({ client: MockClient });
tracker = getTracker();
});
afterEach(() => {
tracker.reset();
jest.clearAllMocks();
vi.clearAllMocks();
});
describe('Services / Files', () => {
describe('createOne', () => {
let service: FilesService;
let superCreateOne: jest.SpyInstance;
let superCreateOne: SpyInstance;
beforeEach(() => {
service = new FilesService({
knex: db,
schema: { collections: {}, relations: [] },
});
superCreateOne = jest.spyOn(ItemsService.prototype, 'createOne').mockImplementation(jest.fn());
superCreateOne = vi.spyOn(ItemsService.prototype, 'createOne').mockReturnValue(Promise.resolve(1));
});
it('throws InvalidPayloadException when "type" is not provided', async () => {
@@ -66,7 +67,7 @@ describe('Integration Tests', () => {
describe('getMetadata', () => {
let service: FilesService;
let exifrParseSpy: jest.SpyInstance<any>;
let exifrParseSpy: SpyInstance<any>;
const sampleMetadata = {
CustomTagA: 'value a',
@@ -75,7 +76,7 @@ describe('Integration Tests', () => {
};
beforeEach(() => {
exifrParseSpy = jest.spyOn(exifr, 'parse');
exifrParseSpy = vi.spyOn(exifr, 'parse');
service = new FilesService({
knex: db,
schema: { collections: {}, relations: [] },

View File

@@ -5,14 +5,15 @@ import { ItemsService } from '../../src/services';
import { sqlFieldFormatter, sqlFieldList } from '../__utils__/items-utils';
import { systemSchema, userSchema } from '../__utils__/schemas';
import { cloneDeep } from 'lodash';
import { describe, beforeAll, afterEach, it, expect, vi } from 'vitest';
jest.mock('../../src/database/index', () => {
return { getDatabaseClient: jest.fn().mockReturnValue('postgres') };
vi.mock('../../src/database/index', () => {
return { getDatabaseClient: vi.fn().mockReturnValue('postgres') };
});
jest.requireMock('../../src/database/index');
vi.mock('../../src/database/index');
describe('Integration Tests', () => {
let db: jest.Mocked<Knex>;
let db: Knex;
let tracker: Tracker;
const schemas: Record<string, any> = {
@@ -21,7 +22,7 @@ describe('Integration Tests', () => {
};
beforeAll(async () => {
db = knex({ client: MockClient }) as jest.Mocked<Knex>;
db = knex({ client: MockClient });
tracker = getTracker();
});

View File

@@ -1,12 +1,13 @@
import { NotificationsService, ItemsService } from '.';
import { afterEach, beforeEach, describe, expect, it, SpyInstance, vi } from 'vitest';
import { ItemsService, NotificationsService } from '.';
jest.mock('../../src/env', () => ({
...jest.requireActual('../../src/env').default,
vi.mock('../../src/env', async () => ({
...(await vi.importActual<any>('../../src/env')),
PUBLIC_URL: '/',
}));
jest.mock('../../src/database/index', () => {
return { __esModule: true, default: jest.fn(), getDatabaseClient: jest.fn().mockReturnValue('postgres') };
vi.mock('../../src/database/index', () => {
return { __esModule: true, default: vi.fn(), getDatabaseClient: vi.fn().mockReturnValue('postgres') };
});
describe('Integration Tests', () => {
@@ -20,16 +21,16 @@ describe('Integration Tests', () => {
});
afterEach(() => {
jest.restoreAllMocks();
vi.restoreAllMocks();
});
describe('createOne', () => {
let superCreateOneSpy: jest.SpyInstance;
let thisSendEmailSpy: jest.SpyInstance;
let superCreateOneSpy: SpyInstance;
let thisSendEmailSpy: SpyInstance;
beforeEach(() => {
superCreateOneSpy = jest.spyOn(ItemsService.prototype, 'createOne').mockImplementation(jest.fn());
thisSendEmailSpy = jest.spyOn(NotificationsService.prototype, 'sendEmail').mockImplementation(jest.fn());
superCreateOneSpy = vi.spyOn(ItemsService.prototype, 'createOne').mockResolvedValue(0);
thisSendEmailSpy = vi.spyOn(NotificationsService.prototype, 'sendEmail').mockResolvedValue();
});
it('create a notification and send email', async () => {
@@ -49,12 +50,12 @@ describe('Integration Tests', () => {
});
describe('createMany', () => {
let superCreateManySpy: jest.SpyInstance;
let thisSendEmailSpy: jest.SpyInstance;
let superCreateManySpy: SpyInstance;
let thisSendEmailSpy: SpyInstance;
beforeEach(() => {
superCreateManySpy = jest.spyOn(ItemsService.prototype, 'createMany').mockImplementation(jest.fn());
thisSendEmailSpy = jest.spyOn(NotificationsService.prototype, 'sendEmail').mockImplementation(jest.fn());
superCreateManySpy = vi.spyOn(ItemsService.prototype, 'createMany').mockResolvedValue([]);
thisSendEmailSpy = vi.spyOn(NotificationsService.prototype, 'sendEmail').mockResolvedValue();
});
it('create many notifications and send email for notification', async () => {
@@ -81,12 +82,12 @@ describe('Integration Tests', () => {
});
describe('sendEmail', () => {
let usersServiceReadOneSpy: jest.SpyInstance;
let mailServiceSendSpy: jest.SpyInstance;
let usersServiceReadOneSpy: SpyInstance;
let mailServiceSendSpy: SpyInstance;
beforeEach(() => {
usersServiceReadOneSpy = jest.spyOn(service.usersService, 'readOne').mockImplementation(jest.fn());
mailServiceSendSpy = jest.spyOn(service.mailService, 'send').mockImplementation(jest.fn());
usersServiceReadOneSpy = vi.spyOn(service.usersService, 'readOne').mockResolvedValue({});
mailServiceSendSpy = vi.spyOn(service.mailService, 'send').mockResolvedValue(0);
});
it('do nothing when there is no recipient', async () => {

View File

@@ -2,18 +2,19 @@ import knex, { Knex } from 'knex';
import { MockClient, Tracker, getTracker } from 'knex-mock-client';
import { PayloadService } from '../../src/services';
import { getHelpers, Helpers } from '../../src/database/helpers';
import { describe, beforeAll, afterEach, it, expect, vi, beforeEach } from 'vitest';
jest.mock('../../src/database/index', () => {
return { getDatabaseClient: jest.fn().mockReturnValue('postgres') };
vi.mock('../../src/database/index', () => {
return { getDatabaseClient: vi.fn().mockReturnValue('postgres') };
});
jest.requireMock('../../src/database/index');
vi.mock('../../src/database/index');
describe('Integration Tests', () => {
let db: jest.Mocked<Knex>;
let db: Knex;
let tracker: Tracker;
beforeAll(async () => {
db = knex({ client: MockClient }) as jest.Mocked<Knex>;
db = knex({ client: MockClient });
tracker = getTracker();
});
@@ -218,7 +219,6 @@ describe('Integration Tests', () => {
],
'read'
);
expect(result).toMatchObject([
{
[dateFieldId]: '2022-01-10',

View File

@@ -1,26 +1,31 @@
import knex, { Knex } from 'knex';
import { getTracker, MockClient, Tracker } from 'knex-mock-client';
import { CollectionsService, FieldsService, RelationsService, SpecificationService } from '../../src/services';
import { describe, beforeAll, afterEach, it, expect, vi, beforeEach } from 'vitest';
jest.mock('../../src/database/index', () => {
return { getDatabaseClient: jest.fn().mockReturnValue('postgres') };
vi.mock('../../src/database/index', async () => {
const actual = await vi.importActual('@directus/shared/utils/node');
return {
...(actual as object),
getDatabaseClient: vi.fn().mockReturnValue('postgres'),
};
});
jest.requireMock('../../src/database/index');
class Client_PG extends MockClient {}
describe('Integration Tests', () => {
let db: jest.Mocked<Knex>;
let db: Knex;
let tracker: Tracker;
beforeAll(async () => {
db = knex({ client: Client_PG }) as jest.Mocked<Knex>;
db = knex({ client: Client_PG });
tracker = getTracker();
});
afterEach(() => {
tracker.reset();
jest.clearAllMocks();
vi.clearAllMocks();
});
describe('Services / Specifications', () => {
@@ -38,8 +43,8 @@ describe('Integration Tests', () => {
describe('schema', () => {
it('returns untyped schema for json fields', async () => {
jest.spyOn(CollectionsService.prototype, 'readByQuery').mockImplementation(
jest.fn().mockReturnValue([
vi.spyOn(CollectionsService.prototype, 'readByQuery').mockImplementation(
vi.fn().mockReturnValue([
{
collection: 'test_table',
meta: {
@@ -60,8 +65,8 @@ describe('Integration Tests', () => {
])
);
jest.spyOn(FieldsService.prototype, 'readAll').mockImplementation(
jest.fn().mockReturnValue([
vi.spyOn(FieldsService.prototype, 'readAll').mockImplementation(
vi.fn().mockReturnValue([
{
collection: 'test_table',
field: 'id',
@@ -80,7 +85,7 @@ describe('Integration Tests', () => {
},
])
);
jest.spyOn(RelationsService.prototype, 'readAll').mockImplementation(jest.fn().mockReturnValue([]));
vi.spyOn(RelationsService.prototype, 'readAll').mockImplementation(vi.fn().mockReturnValue([]));
const spec = await service.oas.generate();
expect(spec.components?.schemas).toEqual({
@@ -121,12 +126,12 @@ describe('Integration Tests', () => {
},
};
jest
.spyOn(CollectionsService.prototype, 'readByQuery')
.mockImplementation(jest.fn().mockReturnValue([collection]));
vi.spyOn(CollectionsService.prototype, 'readByQuery').mockImplementation(
vi.fn().mockReturnValue([collection])
);
jest.spyOn(FieldsService.prototype, 'readAll').mockImplementation(
jest.fn().mockReturnValue([
vi.spyOn(FieldsService.prototype, 'readAll').mockImplementation(
vi.fn().mockReturnValue([
{
collection: collection.collection,
field: 'id',
@@ -137,7 +142,7 @@ describe('Integration Tests', () => {
},
])
);
jest.spyOn(RelationsService.prototype, 'readAll').mockImplementation(jest.fn().mockReturnValue([]));
vi.spyOn(RelationsService.prototype, 'readAll').mockImplementation(vi.fn().mockReturnValue([]));
const spec = await service.oas.generate();

View File

@@ -1,11 +1,12 @@
import { SchemaOverview } from '@directus/shared/types';
import knex, { Knex } from 'knex';
import { getTracker, MockClient, Tracker } from 'knex-mock-client';
import { UsersService, ItemsService } from '.';
import { afterEach, beforeAll, describe, it, vi, expect, MockedFunction } from 'vitest';
import { ItemsService, UsersService } from '.';
import { InvalidPayloadException } from '../exceptions';
jest.mock('../../src/database/index', () => {
return { __esModule: true, default: jest.fn(), getDatabaseClient: jest.fn().mockReturnValue('postgres') };
vi.mock('../../src/database/index', () => {
return { __esModule: true, default: vi.fn(), getDatabaseClient: vi.fn().mockReturnValue('postgres') };
});
const testSchema = {
@@ -39,11 +40,11 @@ const testSchema = {
} as SchemaOverview;
describe('Integration Tests', () => {
let db: jest.Mocked<Knex>;
let db: MockedFunction<Knex>;
let tracker: Tracker;
beforeAll(async () => {
db = knex({ client: MockClient }) as jest.Mocked<Knex>;
db = vi.mocked(knex({ client: MockClient }));
tracker = getTracker();
});
@@ -162,7 +163,7 @@ describe('Integration Tests', () => {
accountability: { role: 'test', admin: false },
});
jest.spyOn(ItemsService.prototype, 'getKeysByQuery').mockImplementation(jest.fn(() => Promise.resolve([1])));
vi.spyOn(ItemsService.prototype, 'getKeysByQuery').mockImplementation(vi.fn(() => Promise.resolve([1])));
const promise = service.updateByQuery({}, { [field]: 'test' });
@@ -184,7 +185,7 @@ describe('Integration Tests', () => {
accountability: { role: 'admin', admin: true },
});
jest.spyOn(ItemsService.prototype, 'getKeysByQuery').mockImplementation(jest.fn(() => Promise.resolve([1])));
vi.spyOn(ItemsService.prototype, 'getKeysByQuery').mockImplementation(vi.fn(() => Promise.resolve([1])));
const promise = service.updateByQuery({}, { [field]: 'test' });
@@ -199,7 +200,7 @@ describe('Integration Tests', () => {
schema: testSchema,
});
jest.spyOn(ItemsService.prototype, 'getKeysByQuery').mockImplementation(jest.fn(() => Promise.resolve([1])));
vi.spyOn(ItemsService.prototype, 'getKeysByQuery').mockImplementation(vi.fn(() => Promise.resolve([1])));
const promise = service.updateByQuery({}, { [field]: 'test' });

View File

@@ -12,28 +12,29 @@ import {
snapshotBeforeDeleteCollection,
} from '../__utils__/snapshots';
import { Snapshot } from '../types';
import { describe, afterEach, it, expect, vi, beforeEach } from 'vitest';
jest.mock('../../src/database/index', () => {
vi.mock('../../src/database/index', () => {
return {
getDatabaseClient: jest.fn().mockReturnValue('postgres'),
getDatabaseClient: vi.fn().mockReturnValue('postgres'),
};
});
jest.requireMock('../../src/database/index');
vi.mock('../../src/database/index');
class Client_PG extends MockClient {}
describe('applySnapshot', () => {
let db: jest.Mocked<Knex>;
let db: Knex;
let tracker: Tracker;
beforeEach(() => {
db = knex({ client: Client_PG }) as jest.Mocked<Knex>;
db = knex({ client: Client_PG });
tracker = getTracker();
});
afterEach(() => {
tracker.reset();
jest.clearAllMocks();
vi.clearAllMocks();
});
describe('Creating new collection(s)', () => {
@@ -102,12 +103,14 @@ describe('applySnapshot', () => {
};
// Stop call to db later on in apply-snapshot
jest.spyOn(getSchema, 'getSchema').mockReturnValue(Promise.resolve(snapshotApplyTestSchema));
vi.spyOn(getSchema, 'getSchema').mockReturnValue(Promise.resolve(snapshotApplyTestSchema));
// We are not actually testing that createOne works, just that is is called correctly
const createOneCollectionSpy = jest
const createOneCollectionSpy = vi
.spyOn(CollectionsService.prototype, 'createOne')
.mockImplementation(jest.fn());
const createFieldSpy = jest.spyOn(FieldsService.prototype, 'createField').mockImplementation(jest.fn());
.mockImplementation(vi.fn().mockReturnValue([]));
const createFieldSpy = vi
.spyOn(FieldsService.prototype, 'createField')
.mockImplementation(vi.fn().mockReturnValue([]));
await applySnapshot(snapshotCreateCollectionNotNested, {
database: db,
@@ -251,12 +254,14 @@ describe('applySnapshot', () => {
};
// Stop call to db later on in apply-snapshot
jest.spyOn(getSchema, 'getSchema').mockReturnValue(Promise.resolve(snapshotApplyTestSchema));
vi.spyOn(getSchema, 'getSchema').mockReturnValue(Promise.resolve(snapshotApplyTestSchema));
// We are not actually testing that createOne works, just that is is called correctly
const createOneCollectionSpy = jest
const createOneCollectionSpy = vi
.spyOn(CollectionsService.prototype, 'createOne')
.mockImplementation(jest.fn());
const createFieldSpy = jest.spyOn(FieldsService.prototype, 'createField').mockImplementation(jest.fn());
.mockImplementation(vi.fn().mockReturnValue([]));
const createFieldSpy = vi
.spyOn(FieldsService.prototype, 'createField')
.mockImplementation(vi.fn().mockReturnValue([]));
await applySnapshot(snapshotCreateCollection, {
database: db,
@@ -285,11 +290,11 @@ describe('applySnapshot', () => {
};
// Stop call to db later on in apply-snapshot
jest.spyOn(getSchema, 'getSchema').mockReturnValue(Promise.resolve(snapshotApplyTestSchema));
vi.spyOn(getSchema, 'getSchema').mockReturnValue(Promise.resolve(snapshotApplyTestSchema));
// We are not actually testing that deleteOne works, just that is is called correctly
const deleteOneCollectionSpy = jest
const deleteOneCollectionSpy = vi
.spyOn(CollectionsService.prototype, 'deleteOne')
.mockImplementation(jest.fn());
.mockImplementation(vi.fn().mockReturnValue([]));
await applySnapshot(snapshotToApply, {
database: db,

View File

@@ -1,10 +1,11 @@
import type { RequestHandler, NextFunction, Request, Response } from 'express';
import type { RequestHandler, Request, Response } from 'express';
import '../../src/types/express.d.ts';
import asyncHandler from './async-handler';
import { expect, vi, test } from 'vitest';
let mockRequest: Partial<Request & { token?: string }>;
let mockResponse: Partial<Response>;
const nextFunction: NextFunction = jest.fn();
const nextFunction = vi.fn();
test('Wraps async middleware in Promise resolve that will catch rejects and pass them to the nextFn', async () => {
const err = new Error('testing');
@@ -13,7 +14,7 @@ test('Wraps async middleware in Promise resolve that will catch rejects and pass
throw err;
};
await asyncHandler(middleware)(mockRequest as Request, mockResponse as Response, nextFunction as NextFunction);
await asyncHandler(middleware)(mockRequest as Request, mockResponse as Response, nextFunction);
expect(nextFunction).toHaveBeenCalledWith(err);
});

View File

@@ -1,4 +1,5 @@
import { calculateFieldDepth } from '../../src/utils/calculate-field-depth';
import { test, expect } from 'vitest';
test('Calculates basic depth', () => {
const filter = {

View File

@@ -1,4 +1,5 @@
import { filterItems } from '../../src/utils/filter-items';
import { describe, test, expect } from 'vitest';
const items = [
{

View File

@@ -1,19 +1,18 @@
import { describe, expect, vi, test } from 'vitest';
import { getAuthProviders } from '../../src/utils/get-auth-providers';
let factoryEnv: { [k: string]: any } = {};
jest.mock(
'../../src/env',
() =>
new Proxy(
{},
{
get(target, prop) {
return factoryEnv[prop as string];
},
}
)
);
import { getAuthProviders } from '../../src/utils/get-auth-providers';
vi.mock('../../src/env', () => ({
default: new Proxy(
{},
{
get(_target, prop) {
return factoryEnv[prop as string];
},
}
),
}));
const scenarios = [
{

View File

@@ -1,5 +1,6 @@
import { Request } from 'express';
import { getCacheKey } from '../../src/utils/get-cache-key';
import { describe, test, expect } from 'vitest';
const restUrl = 'http://localhost/items/example';
const graphQlUrl = 'http://localhost/graphql';

View File

@@ -1,6 +1,7 @@
import { getColumnPath, ColPathProps } from '../../src/utils/get-column-path';
import { InvalidQueryException } from '../../src/exceptions';
import { DeepPartial } from '@directus/shared/types';
import { test, expect } from 'vitest';
/*
{

View File

@@ -1,10 +1,13 @@
import { getConfigFromEnv } from '../../src/utils/get-config-from-env';
import { describe, test, expect, vi } from 'vitest';
jest.mock('../../src/env', () => ({
OBJECT_BRAND__COLOR: 'purple',
OBJECT_BRAND__HEX: '#6644FF',
CAMELCASE_OBJECT__FIRST_KEY: 'firstValue',
CAMELCASE_OBJECT__SECOND_KEY: 'secondValue',
vi.mock('../../src/env', () => ({
default: {
OBJECT_BRAND__COLOR: 'purple',
OBJECT_BRAND__HEX: '#6644FF',
CAMELCASE_OBJECT__FIRST_KEY: 'firstValue',
CAMELCASE_OBJECT__SECOND_KEY: 'secondValue',
},
}));
describe('get config from env', () => {

View File

@@ -1,5 +1,6 @@
import { getRelationInfo } from '../../src/utils/get-relation-info';
import { Relation, DeepPartial } from '@directus/shared/types';
import { describe, expect, it } from 'vitest';
describe('getRelationInfo', () => {
it('Errors on suspiciously long implicit $FOLLOW', () => {

View File

@@ -1,5 +1,6 @@
import { getRelationType } from '../../src/utils/get-relation-type';
import { Relation } from '@directus/shared/types';
import { test, expect } from 'vitest';
test('Returns null if no relation object is included', () => {
const result = getRelationType({ relation: null, collection: null, field: 'test' });

View File

@@ -1,4 +1,5 @@
import { stringByteSize } from '../../src/utils/get-string-byte-size';
import { test, expect } from 'vitest';
test('Returns correct byte size for given input string', () => {
expect(stringByteSize('test')).toBe(4);

View File

@@ -1,5 +1,6 @@
import isDirectusJWT from '../../src/utils/is-directus-jwt';
import jwt from 'jsonwebtoken';
import { test, expect } from 'vitest';
test('Returns false for non JWT string', () => {
const result = isDirectusJWT('test');

View File

@@ -2,6 +2,7 @@ import { verifyAccessJWT } from '../../src/utils/jwt';
import jwt from 'jsonwebtoken';
import { InvalidTokenException, ServiceUnavailableException, TokenExpiredException } from '../../src/exceptions';
import { DirectusTokenPayload } from '../../src/types';
import { test, expect, vi } from 'vitest';
const payload: DirectusTokenPayload = { role: null, app_access: false, admin_access: false };
const secret = 'test-secret';
@@ -32,7 +33,7 @@ Object.entries(InvalidTokenCases).forEach(([title, token]) =>
);
test(`Throws ServiceUnavailableException for unexpected error from jsonwebtoken`, () => {
jest.spyOn(jwt, 'verify').mockImplementation(() => {
vi.spyOn(jwt, 'verify').mockImplementation(() => {
throw new Error();
});

View File

@@ -1,5 +1,6 @@
import { mergePermission } from '../../src/utils/merge-permissions';
import { Permission, Filter } from '@directus/shared/types';
import { describe, expect, test } from 'vitest';
const fullFilter = {} as Filter;
const conditionalFilter = { user: { id: { _eq: '$CURRENT_USER' } } } as Filter;

View File

@@ -1,6 +1,7 @@
import { validateKeys } from '../../src/utils/validate-keys';
import { SchemaOverview } from '@directus/shared/types';
import { v4 as uuid } from 'uuid';
import { describe, expect, it } from 'vitest';
const schema: SchemaOverview = {
collections: {

View File

@@ -11,8 +11,7 @@
"lib": ["es2019"],
"skipLibCheck": true,
"declaration": true,
"resolveJsonModule": true,
"types": ["jest"]
"resolveJsonModule": true
},
"exclude": ["node_modules", "dist", "extensions", "tests"]
"exclude": ["node_modules", "dist", "extensions", "**/__utils__/*.*", "**/__mocks__/*.*", "**/*.test.ts"]
}

15
api/vitest.config.js Normal file
View File

@@ -0,0 +1,15 @@
import { defineConfig } from 'vitest/config';
import path from 'path';
export default defineConfig({
test: {
globalSetup: 'globalSetup.js',
alias: [
// TODO: Remove this after moving to ESM
{
find: '@directus/format-title',
replacement: path.resolve(__dirname, '../app/node_modules/@directus/format-title'),
},
],
},
});