test: add vitest
* feature(root): add vitest and some basic tests * fix(test): after rebase + added referrars test and more sites * fix(test): test broken after rebase * fix(test): provide db url to make prisma happy * fix tests
This commit is contained in:
committed by
GitHub
parent
09c83ddeb4
commit
5445d6309e
@@ -6,7 +6,6 @@
|
|||||||
"testing": "API_PORT=3333 pnpm dev",
|
"testing": "API_PORT=3333 pnpm dev",
|
||||||
"start": "node dist/index.js",
|
"start": "node dist/index.js",
|
||||||
"build": "rm -rf dist && tsup",
|
"build": "rm -rf dist && tsup",
|
||||||
"gen:referrers": "jiti scripts/get-referrers.ts && biome format --write src/referrers/index.ts",
|
|
||||||
"gen:bots": "jiti scripts/get-bots.ts && biome format --write src/bots/bots.ts",
|
"gen:bots": "jiti scripts/get-bots.ts && biome format --write src/bots/bots.ts",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
import fs from 'node:fs';
|
|
||||||
import path from 'node:path';
|
|
||||||
|
|
||||||
// extras
|
|
||||||
const extraReferrers = {
|
|
||||||
'bsky.app': { type: 'social', name: 'Bluesky' },
|
|
||||||
};
|
|
||||||
|
|
||||||
function transform(data: any) {
|
|
||||||
const obj: Record<string, unknown> = {};
|
|
||||||
for (const type in data) {
|
|
||||||
for (const name in data[type]) {
|
|
||||||
const domains = data[type][name].domains ?? [];
|
|
||||||
for (const domain of domains) {
|
|
||||||
obj[domain] = {
|
|
||||||
type,
|
|
||||||
name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
// Get document, or throw exception on error
|
|
||||||
try {
|
|
||||||
const data = await fetch(
|
|
||||||
'https://s3-eu-west-1.amazonaws.com/snowplow-hosted-assets/third-party/referer-parser/referers-latest.json',
|
|
||||||
).then((res) => res.json());
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.resolve(__dirname, '../src/referrers/index.ts'),
|
|
||||||
[
|
|
||||||
'// This file is generated by the script get-referrers.ts',
|
|
||||||
'',
|
|
||||||
'// The data is fetch from snowplow-referer-parser https://github.com/snowplow-referer-parser/referer-parser',
|
|
||||||
`// The orginal referers.yml is based on Piwik's SearchEngines.php and Socials.php, copyright 2012 Matthieu Aubry and available under the GNU General Public License v3.`,
|
|
||||||
'',
|
|
||||||
`const referrers: Record<string, { type: string, name: string }> = ${JSON.stringify(
|
|
||||||
{
|
|
||||||
...transform(data),
|
|
||||||
...extraReferrers,
|
|
||||||
},
|
|
||||||
)} as const;`,
|
|
||||||
'export default referrers;',
|
|
||||||
].join('\n'),
|
|
||||||
'utf-8',
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +0,0 @@
|
|||||||
# Snowplow Referer Parser
|
|
||||||
|
|
||||||
The file index.ts in this dir is generated from snowplows referer database [Snowplow Referer Parser](https://github.com/snowplow-referer-parser/referer-parser).
|
|
||||||
|
|
||||||
The orginal [referers.yml](https://github.com/snowplow-referer-parser/referer-parser/blob/master/resources/referers.yml) is based on Piwik's SearchEngines.php and Socials.php, copyright 2012 Matthieu Aubry and available under the GNU General Public License v3.
|
|
||||||
@@ -2,11 +2,13 @@
|
|||||||
"name": "@openpanel/worker",
|
"name": "@openpanel/worker",
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"test": "vitest",
|
||||||
"dev": "dotenv -e ../../.env -c -v WATCH=1 tsup",
|
"dev": "dotenv -e ../../.env -c -v WATCH=1 tsup",
|
||||||
"testing": "WORKER_PORT=9999 pnpm dev",
|
"testing": "WORKER_PORT=9999 pnpm dev",
|
||||||
"start": "node dist/index.js",
|
"start": "node dist/index.js",
|
||||||
"build": "rm -rf dist && tsup",
|
"build": "rm -rf dist && tsup",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit",
|
||||||
|
"gen:referrers": "jiti scripts/get-referrers.ts && biome format --write ./src/referrers/index.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bull-board/api": "5.21.0",
|
"@bull-board/api": "5.21.0",
|
||||||
|
|||||||
91
apps/worker/scripts/get-referrers.ts
Normal file
91
apps/worker/scripts/get-referrers.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
// extras
|
||||||
|
const extraReferrers = {
|
||||||
|
'zoom.us': { type: 'social', name: 'Zoom' },
|
||||||
|
'apple.com': { type: 'tech', name: 'Apple' },
|
||||||
|
'adobe.com': { type: 'tech', name: 'Adobe' },
|
||||||
|
'figma.com': { type: 'tech', name: 'Figma' },
|
||||||
|
'wix.com': { type: 'commerce', name: 'Wix' },
|
||||||
|
'gmail.com': { type: 'email', name: 'Gmail' },
|
||||||
|
'notion.so': { type: 'tech', name: 'Notion' },
|
||||||
|
'ebay.com': { type: 'commerce', name: 'eBay' },
|
||||||
|
'github.com': { type: 'tech', name: 'GitHub' },
|
||||||
|
'gitlab.com': { type: 'tech', name: 'GitLab' },
|
||||||
|
'slack.com': { type: 'social', name: 'Slack' },
|
||||||
|
'etsy.com': { type: 'commerce', name: 'Etsy' },
|
||||||
|
'bsky.app': { type: 'social', name: 'Bluesky' },
|
||||||
|
'twitch.tv': { type: 'content', name: 'Twitch' },
|
||||||
|
'dropbox.com': { type: 'tech', name: 'Dropbox' },
|
||||||
|
'outlook.com': { type: 'email', name: 'Outlook' },
|
||||||
|
'medium.com': { type: 'content', name: 'Medium' },
|
||||||
|
'paypal.com': { type: 'commerce', name: 'PayPal' },
|
||||||
|
'discord.com': { type: 'social', name: 'Discord' },
|
||||||
|
'stripe.com': { type: 'commerce', name: 'Stripe' },
|
||||||
|
'spotify.com': { type: 'content', name: 'Spotify' },
|
||||||
|
'netflix.com': { type: 'content', name: 'Netflix' },
|
||||||
|
'whatsapp.com': { type: 'social', name: 'WhatsApp' },
|
||||||
|
'shopify.com': { type: 'commerce', name: 'Shopify' },
|
||||||
|
'microsoft.com': { type: 'tech', name: 'Microsoft' },
|
||||||
|
'alibaba.com': { type: 'commerce', name: 'Alibaba' },
|
||||||
|
'telegram.org': { type: 'social', name: 'Telegram' },
|
||||||
|
'substack.com': { type: 'content', name: 'Substack' },
|
||||||
|
'salesforce.com': { type: 'tech', name: 'Salesforce' },
|
||||||
|
'instagram.com': { type: 'social', name: 'Instagram' },
|
||||||
|
'wikipedia.org': { type: 'content', name: 'Wikipedia' },
|
||||||
|
'mastodon.social': { type: 'social', name: 'Mastodon' },
|
||||||
|
'office.com': { type: 'tech', name: 'Microsoft Office' },
|
||||||
|
'squarespace.com': { type: 'commerce', name: 'Squarespace' },
|
||||||
|
'stackoverflow.com': { type: 'tech', name: 'Stack Overflow' },
|
||||||
|
'teams.microsoft.com': { type: 'social', name: 'Microsoft Teams' },
|
||||||
|
};
|
||||||
|
|
||||||
|
function transform(data: any) {
|
||||||
|
const obj: Record<string, unknown> = {};
|
||||||
|
for (const type in data) {
|
||||||
|
for (const name in data[type]) {
|
||||||
|
const domains = data[type][name].domains ?? [];
|
||||||
|
for (const domain of domains) {
|
||||||
|
obj[domain] = {
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// Get document, or throw exception on error
|
||||||
|
try {
|
||||||
|
const data = await fetch(
|
||||||
|
'https://s3-eu-west-1.amazonaws.com/snowplow-hosted-assets/third-party/referer-parser/referers-latest.json',
|
||||||
|
).then((res) => res.json());
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.resolve(__dirname, '../../worker/src/referrers/index.ts'),
|
||||||
|
[
|
||||||
|
'// This file is generated by the script get-referrers.ts',
|
||||||
|
'',
|
||||||
|
'// The data is fetch from snowplow-referer-parser https://github.com/snowplow-referer-parser/referer-parser',
|
||||||
|
`// The orginal referers.yml is based on Piwik's SearchEngines.php and Socials.php, copyright 2012 Matthieu Aubry and available under the GNU General Public License v3.`,
|
||||||
|
'',
|
||||||
|
`const referrers: Record<string, { type: string, name: string }> = ${JSON.stringify(
|
||||||
|
{
|
||||||
|
...transform(data),
|
||||||
|
...extraReferrers,
|
||||||
|
},
|
||||||
|
)} as const;`,
|
||||||
|
'export default referrers;',
|
||||||
|
].join('\n'),
|
||||||
|
'utf-8',
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -121,14 +121,36 @@ export async function incomingEvent(
|
|||||||
|
|
||||||
// if timestamp is from the past we dont want to create a new session
|
// if timestamp is from the past we dont want to create a new session
|
||||||
if (uaInfo.isServer || isTimestampFromThePast) {
|
if (uaInfo.isServer || isTimestampFromThePast) {
|
||||||
const event = profileId
|
const screenView = profileId
|
||||||
? await eventBuffer.getLastScreenView({
|
? await eventBuffer.getLastScreenView({
|
||||||
profileId,
|
profileId,
|
||||||
projectId,
|
projectId,
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const payload = merge(omit(['properties'], event ?? {}), baseEvent);
|
const payload = {
|
||||||
|
...baseEvent,
|
||||||
|
deviceId: screenView?.deviceId ?? '',
|
||||||
|
sessionId: screenView?.sessionId ?? '',
|
||||||
|
referrer: screenView?.referrer ?? undefined,
|
||||||
|
referrerName: screenView?.referrerName ?? undefined,
|
||||||
|
referrerType: screenView?.referrerType ?? undefined,
|
||||||
|
path: screenView?.path ?? baseEvent.path,
|
||||||
|
os: screenView?.os ?? baseEvent.os,
|
||||||
|
osVersion: screenView?.osVersion ?? baseEvent.osVersion,
|
||||||
|
browserVersion: screenView?.browserVersion ?? baseEvent.browserVersion,
|
||||||
|
browser: screenView?.browser ?? baseEvent.browser,
|
||||||
|
device: screenView?.device ?? baseEvent.device,
|
||||||
|
brand: screenView?.brand ?? baseEvent.brand,
|
||||||
|
model: screenView?.model ?? baseEvent.model,
|
||||||
|
city: screenView?.city ?? baseEvent.city,
|
||||||
|
country: screenView?.country ?? baseEvent.country,
|
||||||
|
region: screenView?.region ?? baseEvent.region,
|
||||||
|
longitude: screenView?.longitude ?? baseEvent.longitude,
|
||||||
|
latitude: screenView?.latitude ?? baseEvent.latitude,
|
||||||
|
origin: screenView?.origin ?? baseEvent.origin,
|
||||||
|
};
|
||||||
|
|
||||||
return createEventAndNotify(
|
return createEventAndNotify(
|
||||||
payload as IServiceEvent,
|
payload as IServiceEvent,
|
||||||
job.data.payload,
|
job.data.payload,
|
||||||
@@ -180,7 +202,9 @@ export async function incomingEvent(
|
|||||||
|
|
||||||
const event = await createEventAndNotify(payload, job.data.payload, logger);
|
const event = await createEventAndNotify(payload, job.data.payload, logger);
|
||||||
|
|
||||||
|
if (!sessionEnd) {
|
||||||
await createSessionEndJob({ payload });
|
await createSessionEndJob({ payload });
|
||||||
|
}
|
||||||
|
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,351 +1,380 @@
|
|||||||
// import { type Mock, beforeEach, describe, expect, it, mock } from 'bun:test';
|
import { type IServiceEvent, createEvent } from '@openpanel/db';
|
||||||
// import { getTime, toISOString } from '@openpanel/common';
|
import { eventBuffer } from '@openpanel/db';
|
||||||
// import type { Job } from 'bullmq';
|
import { sessionsQueue } from '@openpanel/queue';
|
||||||
// import { SESSION_TIMEOUT, incomingEvent } from './events.incoming-event';
|
import type { Job } from 'bullmq';
|
||||||
|
import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { incomingEvent } from './events.incoming-event';
|
||||||
|
|
||||||
// const projectId = 'test-project';
|
vi.mock('@openpanel/queue');
|
||||||
// const currentDeviceId = 'device-123';
|
vi.mock('@openpanel/db', async () => {
|
||||||
// const previousDeviceId = 'device-456';
|
const actual = await vi.importActual('@openpanel/db');
|
||||||
// const geo = {
|
return {
|
||||||
// country: 'US',
|
...actual,
|
||||||
// city: 'New York',
|
createEvent: vi.fn(),
|
||||||
// region: 'NY',
|
getLastScreenView: vi.fn(),
|
||||||
// longitude: 0,
|
checkNotificationRulesForEvent: vi.fn().mockResolvedValue(true),
|
||||||
// latitude: 0,
|
eventBuffer: {
|
||||||
// };
|
getLastScreenView: vi.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// const createEvent = mock(() => {});
|
// 30 minutes
|
||||||
// const getLastScreenViewFromProfileId = mock();
|
const SESSION_TIMEOUT = 30 * 60 * 1000;
|
||||||
// // // Mock dependencies
|
const projectId = 'test-project';
|
||||||
// mock.module('@openpanel/db', () => ({
|
const currentDeviceId = 'device-123';
|
||||||
// createEvent,
|
const previousDeviceId = 'device-456';
|
||||||
// getLastScreenViewFromProfileId,
|
const geo = {
|
||||||
// }));
|
country: 'US',
|
||||||
|
city: 'New York',
|
||||||
|
region: 'NY',
|
||||||
|
longitude: 0,
|
||||||
|
latitude: 0,
|
||||||
|
};
|
||||||
|
|
||||||
// const sessionsQueue = { add: mock(() => Promise.resolve({})) };
|
describe('incomingEvent', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
// const findJobByPrefix = mock();
|
it('should create a session start and an event', async () => {
|
||||||
|
const spySessionsQueueAdd = vi.spyOn(sessionsQueue, 'add');
|
||||||
|
const timestamp = new Date();
|
||||||
|
// Mock job data
|
||||||
|
const jobData = {
|
||||||
|
payload: {
|
||||||
|
geo,
|
||||||
|
event: {
|
||||||
|
name: 'test_event',
|
||||||
|
timestamp: timestamp.toISOString(),
|
||||||
|
properties: { __path: 'https://example.com/test' },
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'request-id': '123',
|
||||||
|
'user-agent':
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||||||
|
'openpanel-sdk-name': 'web',
|
||||||
|
'openpanel-sdk-version': '1.0.0',
|
||||||
|
},
|
||||||
|
projectId,
|
||||||
|
currentDeviceId,
|
||||||
|
previousDeviceId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// mock.module('@openpanel/queue', () => ({
|
const job = { data: jobData } as Job;
|
||||||
// sessionsQueue,
|
|
||||||
// findJobByPrefix,
|
|
||||||
// }));
|
|
||||||
|
|
||||||
// const getRedisQueue = mock(() => ({
|
// Execute the job
|
||||||
// keys: mock(() => Promise.resolve([])),
|
await incomingEvent(job);
|
||||||
// }));
|
|
||||||
|
|
||||||
// mock.module('@openpanel/redis', () => ({
|
const event = {
|
||||||
// getRedisQueue,
|
name: 'test_event',
|
||||||
// }));
|
deviceId: currentDeviceId,
|
||||||
|
profileId: '',
|
||||||
|
sessionId: expect.stringMatching(
|
||||||
|
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
||||||
|
),
|
||||||
|
projectId,
|
||||||
|
properties: {
|
||||||
|
__hash: undefined,
|
||||||
|
__query: undefined,
|
||||||
|
__user_agent: jobData.payload.headers['user-agent'],
|
||||||
|
__reqId: jobData.payload.headers['request-id'],
|
||||||
|
},
|
||||||
|
createdAt: timestamp,
|
||||||
|
country: 'US',
|
||||||
|
city: 'New York',
|
||||||
|
region: 'NY',
|
||||||
|
longitude: 0,
|
||||||
|
latitude: 0,
|
||||||
|
os: 'Windows',
|
||||||
|
osVersion: '10',
|
||||||
|
browser: 'Chrome',
|
||||||
|
browserVersion: '91.0.4472.124',
|
||||||
|
device: 'desktop',
|
||||||
|
brand: undefined,
|
||||||
|
model: undefined,
|
||||||
|
duration: 0,
|
||||||
|
path: '/test',
|
||||||
|
origin: 'https://example.com',
|
||||||
|
referrer: '',
|
||||||
|
referrerName: '',
|
||||||
|
referrerType: 'unknown',
|
||||||
|
sdkName: jobData.payload.headers['openpanel-sdk-name'],
|
||||||
|
sdkVersion: jobData.payload.headers['openpanel-sdk-version'],
|
||||||
|
};
|
||||||
|
|
||||||
// describe('incomingEvent', () => {
|
expect(spySessionsQueueAdd).toHaveBeenCalledWith(
|
||||||
// beforeEach(() => {
|
'session',
|
||||||
// createEvent.mockClear();
|
{
|
||||||
// findJobByPrefix.mockClear();
|
type: 'createSessionEnd',
|
||||||
// sessionsQueue.add.mockClear();
|
payload: expect.objectContaining(event),
|
||||||
// getLastScreenViewFromProfileId.mockClear();
|
},
|
||||||
// });
|
{
|
||||||
|
delay: SESSION_TIMEOUT,
|
||||||
|
jobId: `sessionEnd:${projectId}:${currentDeviceId}`,
|
||||||
|
attempts: 3,
|
||||||
|
backoff: {
|
||||||
|
delay: 200,
|
||||||
|
type: 'exponential',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// it('should create a session start and an event', async () => {
|
expect((createEvent as Mock).mock.calls[0]![0]).toStrictEqual({
|
||||||
// const timestamp = new Date();
|
...event,
|
||||||
// // Mock job data
|
createdAt: new Date(timestamp.getTime() - 100),
|
||||||
// const jobData = {
|
name: 'session_start',
|
||||||
// payload: {
|
});
|
||||||
// geo,
|
expect((createEvent as Mock).mock.calls[1]).toMatchObject([event]);
|
||||||
// event: {
|
});
|
||||||
// name: 'test_event',
|
|
||||||
// timestamp: timestamp.toISOString(),
|
|
||||||
// properties: { __path: 'https://example.com/test' },
|
|
||||||
// },
|
|
||||||
// headers: {
|
|
||||||
// 'user-agent':
|
|
||||||
// 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
|
||||||
// 'openpanel-sdk-name': 'web',
|
|
||||||
// 'openpanel-sdk-version': '1.0.0',
|
|
||||||
// },
|
|
||||||
// projectId,
|
|
||||||
// currentDeviceId,
|
|
||||||
// previousDeviceId,
|
|
||||||
// priority: true,
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const job = { data: jobData } as Job;
|
it('should reuse existing session', async () => {
|
||||||
|
const spySessionsQueueAdd = vi.spyOn(sessionsQueue, 'add');
|
||||||
|
const spySessionsQueueGetJob = vi.spyOn(sessionsQueue, 'getJob');
|
||||||
|
|
||||||
// // Execute the job
|
const timestamp = new Date();
|
||||||
// await incomingEvent(job);
|
// Mock job data
|
||||||
|
const jobData = {
|
||||||
|
payload: {
|
||||||
|
geo,
|
||||||
|
event: {
|
||||||
|
name: 'test_event',
|
||||||
|
timestamp: timestamp.toISOString(),
|
||||||
|
properties: { __path: 'https://example.com/test' },
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'request-id': '123',
|
||||||
|
'user-agent':
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||||||
|
'openpanel-sdk-name': 'web',
|
||||||
|
'openpanel-sdk-version': '1.0.0',
|
||||||
|
},
|
||||||
|
projectId,
|
||||||
|
currentDeviceId,
|
||||||
|
previousDeviceId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// const event = {
|
const job = { data: jobData } as Job;
|
||||||
// name: 'test_event',
|
|
||||||
// deviceId: currentDeviceId,
|
|
||||||
// // @ts-expect-error
|
|
||||||
// sessionId: createEvent.mock.calls[1][0].sessionId,
|
|
||||||
// profileId: '',
|
|
||||||
// projectId,
|
|
||||||
// properties: {
|
|
||||||
// __hash: undefined,
|
|
||||||
// __query: undefined,
|
|
||||||
// },
|
|
||||||
// createdAt: timestamp,
|
|
||||||
// country: 'US',
|
|
||||||
// city: 'New York',
|
|
||||||
// region: 'NY',
|
|
||||||
// longitude: 0,
|
|
||||||
// latitude: 0,
|
|
||||||
// os: 'Windows',
|
|
||||||
// osVersion: '10',
|
|
||||||
// browser: 'Chrome',
|
|
||||||
// browserVersion: '91.0.4472.124',
|
|
||||||
// device: 'desktop',
|
|
||||||
// brand: '',
|
|
||||||
// model: '',
|
|
||||||
// duration: 0,
|
|
||||||
// path: '/test',
|
|
||||||
// origin: 'https://example.com',
|
|
||||||
// referrer: '',
|
|
||||||
// referrerName: '',
|
|
||||||
// referrerType: 'unknown',
|
|
||||||
// sdkName: 'web',
|
|
||||||
// sdkVersion: '1.0.0',
|
|
||||||
// };
|
|
||||||
|
|
||||||
// expect(sessionsQueue.add.mock.calls[0]).toMatchObject([
|
const changeDelay = vi.fn();
|
||||||
// 'session',
|
const updateData = vi.fn();
|
||||||
// {
|
spySessionsQueueGetJob.mockResolvedValueOnce({
|
||||||
// type: 'createSessionEnd',
|
getState: vi.fn().mockResolvedValue('delayed'),
|
||||||
// payload: event,
|
updateData,
|
||||||
// },
|
changeDelay,
|
||||||
// {
|
data: {
|
||||||
// delay: SESSION_TIMEOUT,
|
type: 'createSessionEnd',
|
||||||
// jobId: `sessionEnd:${projectId}:${event.deviceId}:${timestamp.getTime()}`,
|
payload: {
|
||||||
// },
|
sessionId: 'session-123',
|
||||||
// ]);
|
deviceId: currentDeviceId,
|
||||||
|
profileId: currentDeviceId,
|
||||||
|
projectId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Partial<Job> as Job);
|
||||||
|
// Execute the job
|
||||||
|
await incomingEvent(job);
|
||||||
|
|
||||||
// // Assertions
|
const event = {
|
||||||
// // Issue: https://github.com/oven-sh/bun/issues/10380
|
name: 'test_event',
|
||||||
// // expect(createEvent).toHaveBeenCalledWith(...)
|
deviceId: currentDeviceId,
|
||||||
// expect(createEvent.mock.calls[0]).toMatchObject([
|
profileId: '',
|
||||||
// {
|
sessionId: 'session-123',
|
||||||
// name: 'session_start',
|
projectId,
|
||||||
// deviceId: currentDeviceId,
|
properties: {
|
||||||
// sessionId: expect.stringMatching(
|
__hash: undefined,
|
||||||
// /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
__query: undefined,
|
||||||
// ),
|
__user_agent: jobData.payload.headers['user-agent'],
|
||||||
// profileId: '',
|
__reqId: jobData.payload.headers['request-id'],
|
||||||
// projectId,
|
},
|
||||||
// properties: {
|
createdAt: timestamp,
|
||||||
// __hash: undefined,
|
country: 'US',
|
||||||
// __query: undefined,
|
city: 'New York',
|
||||||
// },
|
region: 'NY',
|
||||||
// createdAt: new Date(timestamp.getTime() - 100),
|
longitude: 0,
|
||||||
// country: 'US',
|
latitude: 0,
|
||||||
// city: 'New York',
|
os: 'Windows',
|
||||||
// region: 'NY',
|
osVersion: '10',
|
||||||
// longitude: 0,
|
browser: 'Chrome',
|
||||||
// latitude: 0,
|
browserVersion: '91.0.4472.124',
|
||||||
// os: 'Windows',
|
device: 'desktop',
|
||||||
// osVersion: '10',
|
brand: undefined,
|
||||||
// browser: 'Chrome',
|
model: undefined,
|
||||||
// browserVersion: '91.0.4472.124',
|
duration: 0,
|
||||||
// device: 'desktop',
|
path: '/test',
|
||||||
// brand: '',
|
origin: 'https://example.com',
|
||||||
// model: '',
|
referrer: '',
|
||||||
// duration: 0,
|
referrerName: '',
|
||||||
// path: '/test',
|
referrerType: 'unknown',
|
||||||
// origin: 'https://example.com',
|
sdkName: jobData.payload.headers['openpanel-sdk-name'],
|
||||||
// referrer: '',
|
sdkVersion: jobData.payload.headers['openpanel-sdk-version'],
|
||||||
// referrerName: '',
|
};
|
||||||
// referrerType: 'unknown',
|
|
||||||
// sdkName: 'web',
|
|
||||||
// sdkVersion: '1.0.0',
|
|
||||||
// },
|
|
||||||
// ]);
|
|
||||||
// expect(createEvent.mock.calls[1]).toMatchObject([event]);
|
|
||||||
|
|
||||||
// // Add more specific assertions based on the expected behavior
|
expect(spySessionsQueueAdd).toHaveBeenCalledTimes(0);
|
||||||
// });
|
expect(changeDelay).toHaveBeenCalledWith(SESSION_TIMEOUT);
|
||||||
|
expect(createEvent as Mock).toBeCalledTimes(1);
|
||||||
|
expect((createEvent as Mock).mock.calls[0]![0]).toStrictEqual(event);
|
||||||
|
});
|
||||||
|
|
||||||
// it('should reuse existing session', async () => {
|
it('should handle server events (with existing screen view)', async () => {
|
||||||
// // Mock job data
|
const timestamp = new Date();
|
||||||
// const jobData = {
|
const jobData = {
|
||||||
// payload: {
|
payload: {
|
||||||
// geo,
|
geo,
|
||||||
// event: {
|
event: {
|
||||||
// name: 'test_event',
|
name: 'server_event',
|
||||||
// timestamp: new Date().toISOString(),
|
timestamp: timestamp.toISOString(),
|
||||||
// properties: { __path: 'https://example.com/test' },
|
properties: { custom_property: 'test_value' },
|
||||||
// },
|
profileId: 'profile-123',
|
||||||
// headers: {
|
},
|
||||||
// 'user-agent':
|
headers: {
|
||||||
// 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
'user-agent': 'OpenPanel Server/1.0',
|
||||||
// 'openpanel-sdk-name': 'web',
|
'openpanel-sdk-name': 'server',
|
||||||
// 'openpanel-sdk-version': '1.0.0',
|
'openpanel-sdk-version': '1.0.0',
|
||||||
// },
|
'request-id': '123',
|
||||||
// projectId,
|
},
|
||||||
// currentDeviceId,
|
projectId,
|
||||||
// previousDeviceId,
|
currentDeviceId: '',
|
||||||
// priority: false,
|
previousDeviceId: '',
|
||||||
// },
|
},
|
||||||
// };
|
};
|
||||||
// const changeDelay = mock();
|
|
||||||
// findJobByPrefix.mockReturnValueOnce({
|
|
||||||
// changeDelay,
|
|
||||||
// data: {
|
|
||||||
// type: 'createSessionEnd',
|
|
||||||
// payload: {
|
|
||||||
// sessionId: 'session-123',
|
|
||||||
// deviceId: currentDeviceId,
|
|
||||||
// profileId: currentDeviceId,
|
|
||||||
// projectId,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const job = { data: jobData } as Job;
|
const job = { data: jobData } as Job;
|
||||||
|
|
||||||
// // Execute the job
|
const mockLastScreenView = {
|
||||||
// await incomingEvent(job);
|
deviceId: 'last-device-123',
|
||||||
|
sessionId: 'last-session-456',
|
||||||
|
country: 'CA',
|
||||||
|
city: 'Toronto',
|
||||||
|
region: 'ON',
|
||||||
|
os: 'iOS',
|
||||||
|
osVersion: '15.0',
|
||||||
|
browser: 'Safari',
|
||||||
|
browserVersion: '15.0',
|
||||||
|
device: 'mobile',
|
||||||
|
brand: 'Apple',
|
||||||
|
model: 'iPhone',
|
||||||
|
path: '/last-path',
|
||||||
|
origin: 'https://example.com',
|
||||||
|
referrer: 'https://google.com',
|
||||||
|
referrerName: 'Google',
|
||||||
|
referrerType: 'search',
|
||||||
|
};
|
||||||
|
|
||||||
// expect(changeDelay.mock.calls[0]).toMatchObject([SESSION_TIMEOUT]);
|
// Mock the eventBuffer.getLastScreenView method
|
||||||
|
vi.mocked(eventBuffer.getLastScreenView).mockResolvedValueOnce(
|
||||||
|
mockLastScreenView as IServiceEvent,
|
||||||
|
);
|
||||||
|
|
||||||
// // Assertions
|
await incomingEvent(job);
|
||||||
// // Issue: https://github.com/oven-sh/bun/issues/10380
|
|
||||||
// // expect(createEvent).toHaveBeenCalledWith(...)
|
|
||||||
// expect(createEvent.mock.calls[0]).toMatchObject([
|
|
||||||
// {
|
|
||||||
// name: 'test_event',
|
|
||||||
// deviceId: currentDeviceId,
|
|
||||||
// profileId: '',
|
|
||||||
// sessionId: 'session-123',
|
|
||||||
// projectId,
|
|
||||||
// properties: {
|
|
||||||
// __hash: undefined,
|
|
||||||
// __query: undefined,
|
|
||||||
// },
|
|
||||||
// createdAt: expect.any(Date),
|
|
||||||
// country: 'US',
|
|
||||||
// city: 'New York',
|
|
||||||
// region: 'NY',
|
|
||||||
// longitude: 0,
|
|
||||||
// latitude: 0,
|
|
||||||
// os: 'Windows',
|
|
||||||
// osVersion: '10',
|
|
||||||
// browser: 'Chrome',
|
|
||||||
// browserVersion: '91.0.4472.124',
|
|
||||||
// device: 'desktop',
|
|
||||||
// brand: '',
|
|
||||||
// model: '',
|
|
||||||
// duration: 0,
|
|
||||||
// path: '/test',
|
|
||||||
// origin: 'https://example.com',
|
|
||||||
// referrer: '',
|
|
||||||
// referrerName: '',
|
|
||||||
// referrerType: 'unknown',
|
|
||||||
// sdkName: 'web',
|
|
||||||
// sdkVersion: '1.0.0',
|
|
||||||
// },
|
|
||||||
// ]);
|
|
||||||
|
|
||||||
// // Add more specific assertions based on the expected behavior
|
expect((createEvent as Mock).mock.calls[0]![0]).toStrictEqual({
|
||||||
// });
|
name: 'server_event',
|
||||||
|
deviceId: 'last-device-123',
|
||||||
|
sessionId: 'last-session-456',
|
||||||
|
profileId: 'profile-123',
|
||||||
|
projectId,
|
||||||
|
properties: {
|
||||||
|
custom_property: 'test_value',
|
||||||
|
__user_agent: 'OpenPanel Server/1.0',
|
||||||
|
__reqId: '123',
|
||||||
|
__hash: undefined,
|
||||||
|
__query: undefined,
|
||||||
|
},
|
||||||
|
createdAt: timestamp,
|
||||||
|
country: 'CA',
|
||||||
|
city: 'Toronto',
|
||||||
|
region: 'ON',
|
||||||
|
longitude: 0,
|
||||||
|
latitude: 0,
|
||||||
|
os: 'iOS',
|
||||||
|
osVersion: '15.0',
|
||||||
|
browser: 'Safari',
|
||||||
|
browserVersion: '15.0',
|
||||||
|
device: 'mobile',
|
||||||
|
brand: 'Apple',
|
||||||
|
model: 'iPhone',
|
||||||
|
duration: 0,
|
||||||
|
path: '/last-path',
|
||||||
|
origin: 'https://example.com',
|
||||||
|
referrer: 'https://google.com',
|
||||||
|
referrerName: 'Google',
|
||||||
|
referrerType: 'search',
|
||||||
|
sdkName: 'server',
|
||||||
|
sdkVersion: '1.0.0',
|
||||||
|
});
|
||||||
|
|
||||||
// it('should handle server events', async () => {
|
expect(sessionsQueue.add).not.toHaveBeenCalled();
|
||||||
// const timestamp = new Date();
|
});
|
||||||
// const jobData = {
|
|
||||||
// payload: {
|
|
||||||
// geo,
|
|
||||||
// event: {
|
|
||||||
// name: 'server_event',
|
|
||||||
// timestamp: timestamp.toISOString(),
|
|
||||||
// properties: { custom_property: 'test_value' },
|
|
||||||
// profileId: 'profile-123',
|
|
||||||
// },
|
|
||||||
// headers: {
|
|
||||||
// 'user-agent': 'OpenPanel Server/1.0',
|
|
||||||
// 'openpanel-sdk-name': 'server',
|
|
||||||
// 'openpanel-sdk-version': '1.0.0',
|
|
||||||
// },
|
|
||||||
// projectId,
|
|
||||||
// currentDeviceId: '',
|
|
||||||
// previousDeviceId: '',
|
|
||||||
// priority: true,
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const job = { data: jobData } as Job;
|
it('should handle server events (without existing screen view)', async () => {
|
||||||
|
const timestamp = new Date();
|
||||||
|
const jobData = {
|
||||||
|
payload: {
|
||||||
|
geo,
|
||||||
|
event: {
|
||||||
|
name: 'server_event',
|
||||||
|
timestamp: timestamp.toISOString(),
|
||||||
|
properties: { custom_property: 'test_value' },
|
||||||
|
profileId: 'profile-123',
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'user-agent': 'OpenPanel Server/1.0',
|
||||||
|
'openpanel-sdk-name': 'server',
|
||||||
|
'openpanel-sdk-version': '1.0.0',
|
||||||
|
'request-id': '123',
|
||||||
|
},
|
||||||
|
projectId,
|
||||||
|
currentDeviceId: '',
|
||||||
|
previousDeviceId: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// const mockLastScreenView = {
|
const job = { data: jobData } as Job;
|
||||||
// deviceId: 'last-device-123',
|
|
||||||
// sessionId: 'last-session-456',
|
|
||||||
// country: 'CA',
|
|
||||||
// city: 'Toronto',
|
|
||||||
// region: 'ON',
|
|
||||||
// os: 'iOS',
|
|
||||||
// osVersion: '15.0',
|
|
||||||
// browser: 'Safari',
|
|
||||||
// browserVersion: '15.0',
|
|
||||||
// device: 'mobile',
|
|
||||||
// brand: 'Apple',
|
|
||||||
// model: 'iPhone',
|
|
||||||
// path: '/last-path',
|
|
||||||
// origin: 'https://example.com',
|
|
||||||
// referrer: 'https://google.com',
|
|
||||||
// referrerName: 'Google',
|
|
||||||
// referrerType: 'search',
|
|
||||||
// };
|
|
||||||
|
|
||||||
// getLastScreenViewFromProfileId.mockReturnValueOnce(mockLastScreenView);
|
// Mock getLastScreenView to return null
|
||||||
|
vi.mocked(eventBuffer.getLastScreenView).mockResolvedValueOnce(null);
|
||||||
|
|
||||||
// await incomingEvent(job);
|
await incomingEvent(job);
|
||||||
|
|
||||||
// // expect(getLastScreenViewFromProfileId).toHaveBeenCalledWith({
|
expect((createEvent as Mock).mock.calls[0]![0]).toStrictEqual({
|
||||||
// // profileId: 'profile-123',
|
name: 'server_event',
|
||||||
// // projectId,
|
deviceId: '',
|
||||||
// // });
|
sessionId: '',
|
||||||
|
profileId: 'profile-123',
|
||||||
|
projectId,
|
||||||
|
properties: {
|
||||||
|
custom_property: 'test_value',
|
||||||
|
__user_agent: 'OpenPanel Server/1.0',
|
||||||
|
__reqId: '123',
|
||||||
|
__hash: undefined,
|
||||||
|
__query: undefined,
|
||||||
|
},
|
||||||
|
createdAt: timestamp,
|
||||||
|
country: 'US',
|
||||||
|
city: 'New York',
|
||||||
|
region: 'NY',
|
||||||
|
longitude: 0,
|
||||||
|
latitude: 0,
|
||||||
|
os: '',
|
||||||
|
osVersion: '',
|
||||||
|
browser: '',
|
||||||
|
browserVersion: '',
|
||||||
|
device: 'server',
|
||||||
|
brand: '',
|
||||||
|
model: '',
|
||||||
|
duration: 0,
|
||||||
|
path: '',
|
||||||
|
origin: '',
|
||||||
|
referrer: undefined,
|
||||||
|
referrerName: undefined,
|
||||||
|
referrerType: undefined,
|
||||||
|
sdkName: 'server',
|
||||||
|
sdkVersion: '1.0.0',
|
||||||
|
});
|
||||||
|
|
||||||
// expect(createEvent.mock.calls[0]).toMatchObject([
|
expect(sessionsQueue.add).not.toHaveBeenCalled();
|
||||||
// {
|
});
|
||||||
// name: 'server_event',
|
});
|
||||||
// deviceId: 'last-device-123',
|
|
||||||
// sessionId: 'last-session-456',
|
|
||||||
// profileId: 'profile-123',
|
|
||||||
// projectId,
|
|
||||||
// properties: {
|
|
||||||
// custom_property: 'test_value',
|
|
||||||
// user_agent: 'OpenPanel Server/1.0',
|
|
||||||
// },
|
|
||||||
// createdAt: timestamp,
|
|
||||||
// country: 'CA',
|
|
||||||
// city: 'Toronto',
|
|
||||||
// region: 'ON',
|
|
||||||
// longitude: 0,
|
|
||||||
// latitude: 0,
|
|
||||||
// os: 'iOS',
|
|
||||||
// osVersion: '15.0',
|
|
||||||
// browser: 'Safari',
|
|
||||||
// browserVersion: '15.0',
|
|
||||||
// device: 'mobile',
|
|
||||||
// brand: 'Apple',
|
|
||||||
// model: 'iPhone',
|
|
||||||
// duration: 0,
|
|
||||||
// path: '/last-path',
|
|
||||||
// origin: 'https://example.com',
|
|
||||||
// referrer: 'https://google.com',
|
|
||||||
// referrerName: 'Google',
|
|
||||||
// referrerType: 'search',
|
|
||||||
// sdkName: 'server',
|
|
||||||
// sdkVersion: '1.0.0',
|
|
||||||
// },
|
|
||||||
// ]);
|
|
||||||
|
|
||||||
// expect(sessionsQueue.add).not.toHaveBeenCalled();
|
|
||||||
// expect(findJobByPrefix).not.toHaveBeenCalled();
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // Add more test cases for different scenarios:
|
|
||||||
// // - Server events
|
|
||||||
// // - Existing sessions
|
|
||||||
// // - Different priorities
|
|
||||||
// // - Error cases
|
|
||||||
// });
|
|
||||||
|
|||||||
@@ -2663,8 +2663,8 @@ const referrers: Record<string, { type: string; name: string }> = {
|
|||||||
'com.laurencedawson.reddit_sync': { type: 'social', name: 'Reddit' },
|
'com.laurencedawson.reddit_sync': { type: 'social', name: 'Reddit' },
|
||||||
'com.laurencedawson.reddit_sync.pro': { type: 'social', name: 'Reddit' },
|
'com.laurencedawson.reddit_sync.pro': { type: 'social', name: 'Reddit' },
|
||||||
'viadeo.com': { type: 'social', name: 'Viadeo' },
|
'viadeo.com': { type: 'social', name: 'Viadeo' },
|
||||||
'github.com': { type: 'social', name: 'GitHub' },
|
'github.com': { type: 'tech', name: 'GitHub' },
|
||||||
'stackoverflow.com': { type: 'social', name: 'StackOverflow' },
|
'stackoverflow.com': { type: 'tech', name: 'Stack Overflow' },
|
||||||
'gaiaonline.com': { type: 'social', name: 'Gaia Online' },
|
'gaiaonline.com': { type: 'social', name: 'Gaia Online' },
|
||||||
'stumbleupon.com': { type: 'social', name: 'StumbleUpon' },
|
'stumbleupon.com': { type: 'social', name: 'StumbleUpon' },
|
||||||
'inci.sozlukspot.com': { type: 'social', name: 'Inci Sozluk' },
|
'inci.sozlukspot.com': { type: 'social', name: 'Inci Sozluk' },
|
||||||
@@ -2680,5 +2680,38 @@ const referrers: Record<string, { type: string; name: string }> = {
|
|||||||
'hyves.nl': { type: 'social', name: 'Hyves' },
|
'hyves.nl': { type: 'social', name: 'Hyves' },
|
||||||
'paper.li': { type: 'social', name: 'Paper.li' },
|
'paper.li': { type: 'social', name: 'Paper.li' },
|
||||||
'moikrug.ru': { type: 'social', name: 'MoiKrug.ru' },
|
'moikrug.ru': { type: 'social', name: 'MoiKrug.ru' },
|
||||||
|
'zoom.us': { type: 'social', name: 'Zoom' },
|
||||||
|
'apple.com': { type: 'tech', name: 'Apple' },
|
||||||
|
'adobe.com': { type: 'tech', name: 'Adobe' },
|
||||||
|
'figma.com': { type: 'tech', name: 'Figma' },
|
||||||
|
'wix.com': { type: 'commerce', name: 'Wix' },
|
||||||
|
'gmail.com': { type: 'email', name: 'Gmail' },
|
||||||
|
'notion.so': { type: 'tech', name: 'Notion' },
|
||||||
|
'ebay.com': { type: 'commerce', name: 'eBay' },
|
||||||
|
'gitlab.com': { type: 'tech', name: 'GitLab' },
|
||||||
|
'slack.com': { type: 'social', name: 'Slack' },
|
||||||
|
'etsy.com': { type: 'commerce', name: 'Etsy' },
|
||||||
|
'bsky.app': { type: 'social', name: 'Bluesky' },
|
||||||
|
'twitch.tv': { type: 'content', name: 'Twitch' },
|
||||||
|
'dropbox.com': { type: 'tech', name: 'Dropbox' },
|
||||||
|
'outlook.com': { type: 'email', name: 'Outlook' },
|
||||||
|
'medium.com': { type: 'content', name: 'Medium' },
|
||||||
|
'paypal.com': { type: 'commerce', name: 'PayPal' },
|
||||||
|
'discord.com': { type: 'social', name: 'Discord' },
|
||||||
|
'stripe.com': { type: 'commerce', name: 'Stripe' },
|
||||||
|
'spotify.com': { type: 'content', name: 'Spotify' },
|
||||||
|
'netflix.com': { type: 'content', name: 'Netflix' },
|
||||||
|
'whatsapp.com': { type: 'social', name: 'WhatsApp' },
|
||||||
|
'shopify.com': { type: 'commerce', name: 'Shopify' },
|
||||||
|
'microsoft.com': { type: 'tech', name: 'Microsoft' },
|
||||||
|
'alibaba.com': { type: 'commerce', name: 'Alibaba' },
|
||||||
|
'telegram.org': { type: 'social', name: 'Telegram' },
|
||||||
|
'substack.com': { type: 'content', name: 'Substack' },
|
||||||
|
'salesforce.com': { type: 'tech', name: 'Salesforce' },
|
||||||
|
'wikipedia.org': { type: 'content', name: 'Wikipedia' },
|
||||||
|
'mastodon.social': { type: 'social', name: 'Mastodon' },
|
||||||
|
'office.com': { type: 'tech', name: 'Microsoft Office' },
|
||||||
|
'squarespace.com': { type: 'commerce', name: 'Squarespace' },
|
||||||
|
'teams.microsoft.com': { type: 'social', name: 'Microsoft Teams' },
|
||||||
} as const;
|
} as const;
|
||||||
export default referrers;
|
export default referrers;
|
||||||
|
|||||||
117
apps/worker/src/utils/parse-referrer.test.ts
Normal file
117
apps/worker/src/utils/parse-referrer.test.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { getReferrerWithQuery, parseReferrer } from './parse-referrer';
|
||||||
|
|
||||||
|
describe('parseReferrer', () => {
|
||||||
|
it('should handle undefined or empty URLs', () => {
|
||||||
|
expect(parseReferrer(undefined)).toEqual({
|
||||||
|
name: '',
|
||||||
|
type: 'unknown',
|
||||||
|
url: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parseReferrer('')).toEqual({
|
||||||
|
name: '',
|
||||||
|
type: 'unknown',
|
||||||
|
url: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse valid referrer URLs', () => {
|
||||||
|
expect(parseReferrer('https://google.com/search?q=test')).toEqual({
|
||||||
|
name: 'Google',
|
||||||
|
type: 'search',
|
||||||
|
url: 'https://google.com/search?q=test',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle www prefix in hostnames', () => {
|
||||||
|
expect(parseReferrer('https://www.twitter.com/user')).toEqual({
|
||||||
|
name: 'Twitter',
|
||||||
|
type: 'social',
|
||||||
|
url: 'https://www.twitter.com/user',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parseReferrer('https://twitter.com/user')).toEqual({
|
||||||
|
name: 'Twitter',
|
||||||
|
type: 'social',
|
||||||
|
url: 'https://twitter.com/user',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle unknown referrers', () => {
|
||||||
|
expect(parseReferrer('https://unknown-site.com')).toEqual({
|
||||||
|
name: '',
|
||||||
|
type: 'unknown',
|
||||||
|
url: 'https://unknown-site.com',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid URLs', () => {
|
||||||
|
expect(parseReferrer('not-a-url')).toEqual({
|
||||||
|
name: '',
|
||||||
|
type: 'unknown',
|
||||||
|
url: 'not-a-url',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getReferrerWithQuery', () => {
|
||||||
|
it('should handle undefined or empty query', () => {
|
||||||
|
expect(getReferrerWithQuery(undefined)).toBeNull();
|
||||||
|
expect(getReferrerWithQuery({})).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse utm_source parameter', () => {
|
||||||
|
expect(getReferrerWithQuery({ utm_source: 'google' })).toEqual({
|
||||||
|
name: 'Google',
|
||||||
|
type: 'unknown',
|
||||||
|
url: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse ref parameter', () => {
|
||||||
|
expect(getReferrerWithQuery({ ref: 'facebook' })).toEqual({
|
||||||
|
name: 'Facebook',
|
||||||
|
type: 'social',
|
||||||
|
url: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse utm_referrer parameter', () => {
|
||||||
|
expect(getReferrerWithQuery({ utm_referrer: 'twitter' })).toEqual({
|
||||||
|
name: 'Twitter',
|
||||||
|
type: 'social',
|
||||||
|
url: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle case-insensitive matching', () => {
|
||||||
|
expect(getReferrerWithQuery({ utm_source: 'GoOgLe' })).toEqual({
|
||||||
|
name: 'Google',
|
||||||
|
type: 'unknown',
|
||||||
|
url: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle unknown sources', () => {
|
||||||
|
expect(getReferrerWithQuery({ utm_source: 'unknown-source' })).toEqual({
|
||||||
|
name: 'unknown-source',
|
||||||
|
type: 'unknown',
|
||||||
|
url: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prioritize utm_source over ref and utm_referrer', () => {
|
||||||
|
expect(
|
||||||
|
getReferrerWithQuery({
|
||||||
|
utm_source: 'google',
|
||||||
|
ref: 'facebook',
|
||||||
|
utm_referrer: 'twitter',
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
name: 'Google',
|
||||||
|
type: 'unknown',
|
||||||
|
url: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -76,7 +76,7 @@ export async function getSessionEnd({
|
|||||||
sessionEnd.job.data.payload.deviceId;
|
sessionEnd.job.data.payload.deviceId;
|
||||||
|
|
||||||
const eventIsIdentified =
|
const eventIsIdentified =
|
||||||
sessionEnd.job.data.payload.profileId !== profileId;
|
profileId && sessionEnd.job.data.payload.profileId !== profileId;
|
||||||
|
|
||||||
if (existingSessionIsAnonymous && eventIsIdentified) {
|
if (existingSessionIsAnonymous && eventIsIdentified) {
|
||||||
await sessionEnd.job.updateData({
|
await sessionEnd.job.updateData({
|
||||||
|
|||||||
3
apps/worker/vitest.config.ts
Normal file
3
apps/worker/vitest.config.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { getSharedVitestConfig } from '../../vitest.shared';
|
||||||
|
|
||||||
|
export default getSharedVitestConfig({ __dirname });
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
"author": "Carl-Gerhard Lindesvärd",
|
"author": "Carl-Gerhard Lindesvärd",
|
||||||
"packageManager": "pnpm@9.15.0",
|
"packageManager": "pnpm@9.15.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"test": "vitest",
|
||||||
"dock:up": "docker compose up -d",
|
"dock:up": "docker compose up -d",
|
||||||
"dock:down": "docker compose down",
|
"dock:down": "docker compose down",
|
||||||
"dock:ch": "docker compose exec -it op-ch clickhouse-client -d openpanel",
|
"dock:ch": "docker compose exec -it op-ch clickhouse-client -d openpanel",
|
||||||
@@ -25,7 +26,7 @@
|
|||||||
"update-simple-git-hooks": "npx simple-git-hooks"
|
"update-simple-git-hooks": "npx simple-git-hooks"
|
||||||
},
|
},
|
||||||
"simple-git-hooks": {
|
"simple-git-hooks": {
|
||||||
"pre-push": "pnpm typecheck"
|
"pre-push": "pnpm typecheck && pnpm test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hyperdx/node-opentelemetry": "^0.8.1",
|
"@hyperdx/node-opentelemetry": "^0.8.1",
|
||||||
@@ -48,6 +49,7 @@
|
|||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.9.1",
|
"@biomejs/biome": "1.9.1",
|
||||||
"simple-git-hooks": "^2.12.1"
|
"simple-git-hooks": "^2.12.1",
|
||||||
|
"vitest": "^3.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"test": "vitest",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
170
packages/common/server/parser-user-agent.test.ts
Normal file
170
packages/common/server/parser-user-agent.test.ts
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { getDevice, parseUserAgent } from './parser-user-agent';
|
||||||
|
|
||||||
|
describe('parseUserAgent', () => {
|
||||||
|
it('should return server UA for null/undefined input', () => {
|
||||||
|
const serverUa = {
|
||||||
|
isServer: true,
|
||||||
|
device: 'server',
|
||||||
|
os: '',
|
||||||
|
osVersion: '',
|
||||||
|
browser: '',
|
||||||
|
browserVersion: '',
|
||||||
|
brand: '',
|
||||||
|
model: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(parseUserAgent(null)).toEqual(serverUa);
|
||||||
|
expect(parseUserAgent(undefined)).toEqual(serverUa);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse iPhone user agents', () => {
|
||||||
|
const ua =
|
||||||
|
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1';
|
||||||
|
|
||||||
|
expect(parseUserAgent(ua)).toEqual({
|
||||||
|
isServer: false,
|
||||||
|
device: 'mobile',
|
||||||
|
os: 'iOS',
|
||||||
|
osVersion: '16.5',
|
||||||
|
browser: 'Mobile Safari',
|
||||||
|
browserVersion: '16.5',
|
||||||
|
brand: 'Apple',
|
||||||
|
model: 'iPhone',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse iPad user agents', () => {
|
||||||
|
const ua =
|
||||||
|
'Mozilla/5.0 (iPad; CPU OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1';
|
||||||
|
|
||||||
|
expect(parseUserAgent(ua)).toEqual({
|
||||||
|
isServer: false,
|
||||||
|
device: 'tablet',
|
||||||
|
os: 'iOS',
|
||||||
|
osVersion: '16.5',
|
||||||
|
browser: 'Mobile Safari',
|
||||||
|
browserVersion: '16.5',
|
||||||
|
brand: 'Apple',
|
||||||
|
model: 'iPad',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse iPadOS user agents', () => {
|
||||||
|
const ua =
|
||||||
|
'Mozilla/5.0 (iPad; iPadOS 18_0; like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/18.0';
|
||||||
|
|
||||||
|
expect(parseUserAgent(ua)).toEqual({
|
||||||
|
isServer: false,
|
||||||
|
device: 'tablet',
|
||||||
|
os: 'Mac OS',
|
||||||
|
osVersion: '18.0',
|
||||||
|
browser: 'WebKit',
|
||||||
|
browserVersion: '605.1.15',
|
||||||
|
brand: 'Apple',
|
||||||
|
model: 'iPad',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse desktop Chrome user agents', () => {
|
||||||
|
const ua =
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36';
|
||||||
|
|
||||||
|
expect(parseUserAgent(ua)).toEqual({
|
||||||
|
isServer: false,
|
||||||
|
device: 'desktop',
|
||||||
|
os: 'Windows',
|
||||||
|
osVersion: '10',
|
||||||
|
browser: 'Chrome',
|
||||||
|
browserVersion: '91.0.4472.124',
|
||||||
|
brand: undefined,
|
||||||
|
model: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle server user agents', () => {
|
||||||
|
const serverUas = [
|
||||||
|
'Go-http-client/1.0',
|
||||||
|
'Go Http Client/1.0',
|
||||||
|
'node-fetch/1.0',
|
||||||
|
];
|
||||||
|
|
||||||
|
const expectedResult = {
|
||||||
|
isServer: true,
|
||||||
|
device: 'server',
|
||||||
|
os: '',
|
||||||
|
osVersion: '',
|
||||||
|
browser: '',
|
||||||
|
browserVersion: '',
|
||||||
|
brand: '',
|
||||||
|
model: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
serverUas.forEach((ua) => {
|
||||||
|
expect(parseUserAgent(ua)).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply overrides when provided', () => {
|
||||||
|
const ua =
|
||||||
|
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15';
|
||||||
|
const overrides = {
|
||||||
|
__os: 'Custom OS',
|
||||||
|
__osVersion: '1.0',
|
||||||
|
__browser: 'Custom Browser',
|
||||||
|
__browserVersion: '2.0',
|
||||||
|
__device: 'custom-device',
|
||||||
|
__brand: 'Custom Brand',
|
||||||
|
__model: 'Custom Model',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(parseUserAgent(ua, overrides)).toEqual({
|
||||||
|
isServer: false,
|
||||||
|
device: 'custom-device',
|
||||||
|
os: 'Custom OS',
|
||||||
|
osVersion: '1.0',
|
||||||
|
browser: 'Custom Browser',
|
||||||
|
browserVersion: '2.0',
|
||||||
|
brand: 'Custom Brand',
|
||||||
|
model: 'Custom Model',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getDevice', () => {
|
||||||
|
it('should detect mobile devices', () => {
|
||||||
|
const mobileUas = [
|
||||||
|
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15',
|
||||||
|
'Mozilla/5.0 (Linux; Android 10; SM-A505FN) AppleWebKit/537.36',
|
||||||
|
'Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Build/IML74K) AppleWebkit/534.30',
|
||||||
|
];
|
||||||
|
|
||||||
|
mobileUas.forEach((ua) => {
|
||||||
|
expect(getDevice(ua)).toBe('mobile');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect tablet devices', () => {
|
||||||
|
const tabletUas = [
|
||||||
|
'Mozilla/5.0 (iPad; CPU OS 16_5 like Mac OS X) AppleWebKit/605.1.15',
|
||||||
|
'Mozilla/5.0 (Linux; Android 10.0; Tablet; rv:68.0) Gecko/68.0 Firefox/68.0',
|
||||||
|
'Mozilla/5.0 (Linux; Android 7.0; SM-T827R4 Build/NRD90M)',
|
||||||
|
];
|
||||||
|
|
||||||
|
tabletUas.forEach((ua) => {
|
||||||
|
expect(getDevice(ua)).toBe('tablet');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should default to desktop for non-mobile/tablet devices', () => {
|
||||||
|
const desktopUas = [
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||||
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
||||||
|
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
|
||||||
|
];
|
||||||
|
|
||||||
|
desktopUas.forEach((ua) => {
|
||||||
|
expect(getDevice(ua)).toBe('desktop');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -109,6 +109,21 @@ function isServer(res: UAParser.IResult) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getDevice(ua: string) {
|
export function getDevice(ua: string) {
|
||||||
|
// Samsung mobile devices use SM-[A,G,N,etc]XXX pattern
|
||||||
|
if (/SM-[ABDEFGJMNRWZ][0-9]+/i.test(ua)) {
|
||||||
|
return 'mobile';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Samsung tablets use SM-TXXX pattern
|
||||||
|
if (/SM-T[0-9]+/i.test(ua)) {
|
||||||
|
return 'tablet';
|
||||||
|
}
|
||||||
|
|
||||||
|
// LG mobile devices use LG-XXXX pattern
|
||||||
|
if (/LG-[A-Z0-9]+/i.test(ua)) {
|
||||||
|
return 'mobile';
|
||||||
|
}
|
||||||
|
|
||||||
const mobile1 =
|
const mobile1 =
|
||||||
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(
|
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(
|
||||||
ua,
|
ua,
|
||||||
@@ -118,9 +133,11 @@ export function getDevice(ua: string) {
|
|||||||
ua.slice(0, 4),
|
ua.slice(0, 4),
|
||||||
);
|
);
|
||||||
const tablet =
|
const tablet =
|
||||||
/tablet|ipad|android(?!.*mobile)|xoom|sch-i800|kindle|silk|playbook/i.test(
|
/tablet|ipad|xoom|sch-i800|kindle|silk|playbook/i.test(ua) ||
|
||||||
ua,
|
(/android/i.test(ua) &&
|
||||||
);
|
!/mobile/i.test(ua) &&
|
||||||
|
!/SM-[ABDEFGJMNRWZ][0-9]+/i.test(ua) &&
|
||||||
|
!/LG-[A-Z0-9]+/i.test(ua));
|
||||||
|
|
||||||
if (mobile1 || mobile2) {
|
if (mobile1 || mobile2) {
|
||||||
return 'mobile';
|
return 'mobile';
|
||||||
|
|||||||
3
packages/common/vitest.config.ts
Normal file
3
packages/common/vitest.config.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { getSharedVitestConfig } from '../../vitest.shared';
|
||||||
|
|
||||||
|
export default getSharedVitestConfig({ __dirname });
|
||||||
@@ -85,7 +85,7 @@ export function createLogger({ name }: { name: string }): ILogger {
|
|||||||
level: logLevel,
|
level: logLevel,
|
||||||
format,
|
format,
|
||||||
transports,
|
transports,
|
||||||
// silent: true,
|
silent: process.env.NODE_ENV === 'test',
|
||||||
// Add ISO levels of logging from PINO
|
// Add ISO levels of logging from PINO
|
||||||
levels: Object.assign(
|
levels: Object.assign(
|
||||||
{ fatal: 0, warn: 4, trace: 7 },
|
{ fatal: 0, warn: 4, trace: 7 },
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
export * from './src/queues';
|
export * from './src/queues';
|
||||||
export type * from './src/queues';
|
export type * from './src/queues';
|
||||||
export { findJobByPrefix } from './src/utils';
|
|
||||||
export type { JobsOptions } from 'bullmq';
|
export type { JobsOptions } from 'bullmq';
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
import type { Queue } from 'bullmq';
|
|
||||||
|
|
||||||
export async function findJobByPrefix<T>(
|
|
||||||
queue: Queue<T, any, string>,
|
|
||||||
keys: string[],
|
|
||||||
matcher: string,
|
|
||||||
) {
|
|
||||||
const getTime = (val?: string) => {
|
|
||||||
if (!val) return null;
|
|
||||||
const match = val.match(/:(\d+)$/);
|
|
||||||
return match?.[1] ? Number.parseInt(match[1], 10) : null;
|
|
||||||
};
|
|
||||||
const filtered = keys
|
|
||||||
.filter((key) => key.includes(matcher))
|
|
||||||
.filter((key) => getTime(key));
|
|
||||||
filtered.sort((a, b) => {
|
|
||||||
const aTime = getTime(a);
|
|
||||||
const bTime = getTime(b);
|
|
||||||
if (aTime === null) return 1;
|
|
||||||
if (bTime === null) return -1;
|
|
||||||
return aTime - bTime;
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getJob(index: number) {
|
|
||||||
if (index >= filtered.length) return null;
|
|
||||||
|
|
||||||
const key = filtered[index]?.replace(/^bull:(\w+):/, '');
|
|
||||||
// return new Promise((resolve) => )
|
|
||||||
if (key) {
|
|
||||||
const job = await queue.getJob(key);
|
|
||||||
if ((await job?.getState()) === 'delayed') {
|
|
||||||
return job;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return getJob(index + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getJob(0);
|
|
||||||
}
|
|
||||||
284
pnpm-lock.yaml
generated
284
pnpm-lock.yaml
generated
@@ -36,6 +36,9 @@ importers:
|
|||||||
simple-git-hooks:
|
simple-git-hooks:
|
||||||
specifier: ^2.12.1
|
specifier: ^2.12.1
|
||||||
version: 2.12.1
|
version: 2.12.1
|
||||||
|
vitest:
|
||||||
|
specifier: ^3.0.4
|
||||||
|
version: 3.1.3(@types/debug@4.1.12)(@types/node@20.14.8)(jiti@2.4.1)(terser@5.27.1)
|
||||||
|
|
||||||
apps/api:
|
apps/api:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6649,6 +6652,35 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0
|
graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0
|
||||||
|
|
||||||
|
'@vitest/expect@3.1.3':
|
||||||
|
resolution: {integrity: sha512-7FTQQuuLKmN1Ig/h+h/GO+44Q1IlglPlR2es4ab7Yvfx+Uk5xsv+Ykk+MEt/M2Yn/xGmzaLKxGw2lgy2bwuYqg==}
|
||||||
|
|
||||||
|
'@vitest/mocker@3.1.3':
|
||||||
|
resolution: {integrity: sha512-PJbLjonJK82uCWHjzgBJZuR7zmAOrSvKk1QBxrennDIgtH4uK0TB1PvYmc0XBCigxxtiAVPfWtAdy4lpz8SQGQ==}
|
||||||
|
peerDependencies:
|
||||||
|
msw: ^2.4.9
|
||||||
|
vite: ^5.0.0 || ^6.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
msw:
|
||||||
|
optional: true
|
||||||
|
vite:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@vitest/pretty-format@3.1.3':
|
||||||
|
resolution: {integrity: sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==}
|
||||||
|
|
||||||
|
'@vitest/runner@3.1.3':
|
||||||
|
resolution: {integrity: sha512-Tae+ogtlNfFei5DggOsSUvkIaSuVywujMj6HzR97AHK6XK8i3BuVyIifWAm/sE3a15lF5RH9yQIrbXYuo0IFyA==}
|
||||||
|
|
||||||
|
'@vitest/snapshot@3.1.3':
|
||||||
|
resolution: {integrity: sha512-XVa5OPNTYUsyqG9skuUkFzAeFnEzDp8hQu7kZ0N25B1+6KjGm4hWLtURyBbsIAOekfWQ7Wuz/N/XXzgYO3deWQ==}
|
||||||
|
|
||||||
|
'@vitest/spy@3.1.3':
|
||||||
|
resolution: {integrity: sha512-x6w+ctOEmEXdWaa6TO4ilb7l9DxPR5bwEb6hILKuxfU1NqWT2mpJD9NJN7t3OTfxmVlOMrvtoFJGdgyzZ605lQ==}
|
||||||
|
|
||||||
|
'@vitest/utils@3.1.3':
|
||||||
|
resolution: {integrity: sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg==}
|
||||||
|
|
||||||
'@xmldom/xmldom@0.7.13':
|
'@xmldom/xmldom@0.7.13':
|
||||||
resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==}
|
resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
@@ -6843,6 +6875,10 @@ packages:
|
|||||||
asap@2.0.6:
|
asap@2.0.6:
|
||||||
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
|
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
|
||||||
|
|
||||||
|
assertion-error@2.0.1:
|
||||||
|
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
ast-types@0.15.2:
|
ast-types@0.15.2:
|
||||||
resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==}
|
resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -7170,6 +7206,10 @@ packages:
|
|||||||
ccount@2.0.1:
|
ccount@2.0.1:
|
||||||
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
||||||
|
|
||||||
|
chai@5.2.0:
|
||||||
|
resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
chalk@2.4.2:
|
chalk@2.4.2:
|
||||||
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
|
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -7209,6 +7249,10 @@ packages:
|
|||||||
charenc@0.0.2:
|
charenc@0.0.2:
|
||||||
resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
|
resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
|
||||||
|
|
||||||
|
check-error@2.1.1:
|
||||||
|
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
|
||||||
|
engines: {node: '>= 16'}
|
||||||
|
|
||||||
cheerio-select@2.1.0:
|
cheerio-select@2.1.0:
|
||||||
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
|
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
|
||||||
|
|
||||||
@@ -7810,6 +7854,10 @@ packages:
|
|||||||
decode-named-character-reference@1.0.2:
|
decode-named-character-reference@1.0.2:
|
||||||
resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
|
resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
|
||||||
|
|
||||||
|
deep-eql@5.0.2:
|
||||||
|
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
deep-extend@0.6.0:
|
deep-extend@0.6.0:
|
||||||
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
||||||
engines: {node: '>=4.0.0'}
|
engines: {node: '>=4.0.0'}
|
||||||
@@ -8229,6 +8277,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
expect-type@1.2.1:
|
||||||
|
resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
expo-application@5.3.1:
|
expo-application@5.3.1:
|
||||||
resolution: {integrity: sha512-HR2+K+Hm33vLw/TfbFaHrvUbRRNRco8R+3QaCKy7eJC2LFfT05kZ15ynGaKfB5DJ/oqPV3mxXVR/EfwmE++hoA==}
|
resolution: {integrity: sha512-HR2+K+Hm33vLw/TfbFaHrvUbRRNRco8R+3QaCKy7eJC2LFfT05kZ15ynGaKfB5DJ/oqPV3mxXVR/EfwmE++hoA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -9692,6 +9744,9 @@ packages:
|
|||||||
lottie-web@5.12.2:
|
lottie-web@5.12.2:
|
||||||
resolution: {integrity: sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg==}
|
resolution: {integrity: sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg==}
|
||||||
|
|
||||||
|
loupe@3.1.3:
|
||||||
|
resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==}
|
||||||
|
|
||||||
lowlight@1.20.0:
|
lowlight@1.20.0:
|
||||||
resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==}
|
resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==}
|
||||||
|
|
||||||
@@ -10719,6 +10774,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
pathe@2.0.3:
|
||||||
|
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||||
|
|
||||||
|
pathval@2.0.0:
|
||||||
|
resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
|
||||||
|
engines: {node: '>= 14.16'}
|
||||||
|
|
||||||
peberminta@0.9.0:
|
peberminta@0.9.0:
|
||||||
resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
|
resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
|
||||||
|
|
||||||
@@ -11853,6 +11915,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==}
|
resolution: {integrity: sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
siginfo@2.0.0:
|
||||||
|
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||||
|
|
||||||
signal-exit@3.0.7:
|
signal-exit@3.0.7:
|
||||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||||
|
|
||||||
@@ -11974,6 +12039,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
|
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
stackback@0.0.2:
|
||||||
|
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||||
|
|
||||||
stackframe@1.3.4:
|
stackframe@1.3.4:
|
||||||
resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==}
|
resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==}
|
||||||
|
|
||||||
@@ -11995,6 +12063,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
std-env@3.9.0:
|
||||||
|
resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
|
||||||
|
|
||||||
stop-iteration-iterator@1.0.0:
|
stop-iteration-iterator@1.0.0:
|
||||||
resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==}
|
resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -12290,6 +12361,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-27BIW0dIWTYYoWNnqSmoNMKe5WIbkXsc0xaCQHd3/3xT2XMuMJrzHdrO9QBFR14emBz1Bu0dOAs2sCBBrvgPQA==}
|
resolution: {integrity: sha512-27BIW0dIWTYYoWNnqSmoNMKe5WIbkXsc0xaCQHd3/3xT2XMuMJrzHdrO9QBFR14emBz1Bu0dOAs2sCBBrvgPQA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
tinybench@2.9.0:
|
||||||
|
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||||
|
|
||||||
tinyexec@0.3.2:
|
tinyexec@0.3.2:
|
||||||
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
||||||
|
|
||||||
@@ -12297,6 +12371,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==}
|
resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
tinypool@1.0.2:
|
||||||
|
resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==}
|
||||||
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
|
|
||||||
|
tinyrainbow@2.0.0:
|
||||||
|
resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
|
tinyspy@3.0.2:
|
||||||
|
resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
tmp@0.0.33:
|
tmp@0.0.33:
|
||||||
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
|
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
|
||||||
engines: {node: '>=0.6.0'}
|
engines: {node: '>=0.6.0'}
|
||||||
@@ -12757,6 +12843,11 @@ packages:
|
|||||||
victory-vendor@36.9.1:
|
victory-vendor@36.9.1:
|
||||||
resolution: {integrity: sha512-+pZIP+U3pEJdDCeFmsXwHzV7vNHQC/eIbHklfe2ZCZqayYRH7lQbHcVgsJ0XOOv27hWs4jH4MONgXxHMObTMSA==}
|
resolution: {integrity: sha512-+pZIP+U3pEJdDCeFmsXwHzV7vNHQC/eIbHklfe2ZCZqayYRH7lQbHcVgsJ0XOOv27hWs4jH4MONgXxHMObTMSA==}
|
||||||
|
|
||||||
|
vite-node@3.1.3:
|
||||||
|
resolution: {integrity: sha512-uHV4plJ2IxCl4u1up1FQRrqclylKAogbtBfOTwcuJ28xFi+89PZ57BRh+naIRvH70HPwxy5QHYzg1OrEaC7AbA==}
|
||||||
|
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
vite@6.3.3:
|
vite@6.3.3:
|
||||||
resolution: {integrity: sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==}
|
resolution: {integrity: sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==}
|
||||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||||
@@ -12805,6 +12896,34 @@ packages:
|
|||||||
vite:
|
vite:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
vitest@3.1.3:
|
||||||
|
resolution: {integrity: sha512-188iM4hAHQ0km23TN/adso1q5hhwKqUpv+Sd6p5sOuh6FhQnRNW3IsiIpvxqahtBabsJ2SLZgmGSpcYK4wQYJw==}
|
||||||
|
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
'@edge-runtime/vm': '*'
|
||||||
|
'@types/debug': ^4.1.12
|
||||||
|
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
|
||||||
|
'@vitest/browser': 3.1.3
|
||||||
|
'@vitest/ui': 3.1.3
|
||||||
|
happy-dom: '*'
|
||||||
|
jsdom: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@edge-runtime/vm':
|
||||||
|
optional: true
|
||||||
|
'@types/debug':
|
||||||
|
optional: true
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
'@vitest/browser':
|
||||||
|
optional: true
|
||||||
|
'@vitest/ui':
|
||||||
|
optional: true
|
||||||
|
happy-dom:
|
||||||
|
optional: true
|
||||||
|
jsdom:
|
||||||
|
optional: true
|
||||||
|
|
||||||
vlq@1.0.1:
|
vlq@1.0.1:
|
||||||
resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==}
|
resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==}
|
||||||
|
|
||||||
@@ -12871,6 +12990,11 @@ packages:
|
|||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
why-is-node-running@2.3.0:
|
||||||
|
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
wide-align@1.1.5:
|
wide-align@1.1.5:
|
||||||
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
||||||
|
|
||||||
@@ -14701,7 +14825,7 @@ snapshots:
|
|||||||
'@expo/sdk-runtime-versions': 1.0.0
|
'@expo/sdk-runtime-versions': 1.0.0
|
||||||
'@react-native/normalize-color': 2.1.0
|
'@react-native/normalize-color': 2.1.0
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
debug: 4.3.7
|
debug: 4.4.0
|
||||||
find-up: 5.0.0
|
find-up: 5.0.0
|
||||||
getenv: 1.0.0
|
getenv: 1.0.0
|
||||||
glob: 7.1.6
|
glob: 7.1.6
|
||||||
@@ -14764,7 +14888,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@expo/spawn-async': 1.7.2
|
'@expo/spawn-async': 1.7.2
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
debug: 4.3.7
|
debug: 4.4.0
|
||||||
find-up: 5.0.0
|
find-up: 5.0.0
|
||||||
minimatch: 3.1.2
|
minimatch: 3.1.2
|
||||||
p-limit: 3.1.0
|
p-limit: 3.1.0
|
||||||
@@ -15220,7 +15344,7 @@ snapshots:
|
|||||||
'@jridgewell/gen-mapping@0.3.5':
|
'@jridgewell/gen-mapping@0.3.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/set-array': 1.2.1
|
'@jridgewell/set-array': 1.2.1
|
||||||
'@jridgewell/sourcemap-codec': 1.4.15
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
'@jridgewell/trace-mapping': 0.3.25
|
'@jridgewell/trace-mapping': 0.3.25
|
||||||
|
|
||||||
'@jridgewell/resolve-uri@3.1.2': {}
|
'@jridgewell/resolve-uri@3.1.2': {}
|
||||||
@@ -15241,12 +15365,12 @@ snapshots:
|
|||||||
'@jridgewell/trace-mapping@0.3.22':
|
'@jridgewell/trace-mapping@0.3.22':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.4.15
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
|
|
||||||
'@jridgewell/trace-mapping@0.3.25':
|
'@jridgewell/trace-mapping@0.3.25':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.4.15
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
|
|
||||||
'@js-sdsl/ordered-map@4.4.2': {}
|
'@js-sdsl/ordered-map@4.4.2': {}
|
||||||
|
|
||||||
@@ -19067,6 +19191,46 @@ snapshots:
|
|||||||
graphql: 15.8.0
|
graphql: 15.8.0
|
||||||
wonka: 4.0.15
|
wonka: 4.0.15
|
||||||
|
|
||||||
|
'@vitest/expect@3.1.3':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/spy': 3.1.3
|
||||||
|
'@vitest/utils': 3.1.3
|
||||||
|
chai: 5.2.0
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
|
||||||
|
'@vitest/mocker@3.1.3(vite@6.3.3(@types/node@20.14.8)(jiti@2.4.1)(terser@5.27.1))':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/spy': 3.1.3
|
||||||
|
estree-walker: 3.0.3
|
||||||
|
magic-string: 0.30.17
|
||||||
|
optionalDependencies:
|
||||||
|
vite: 6.3.3(@types/node@20.14.8)(jiti@2.4.1)(terser@5.27.1)
|
||||||
|
|
||||||
|
'@vitest/pretty-format@3.1.3':
|
||||||
|
dependencies:
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
|
||||||
|
'@vitest/runner@3.1.3':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/utils': 3.1.3
|
||||||
|
pathe: 2.0.3
|
||||||
|
|
||||||
|
'@vitest/snapshot@3.1.3':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/pretty-format': 3.1.3
|
||||||
|
magic-string: 0.30.17
|
||||||
|
pathe: 2.0.3
|
||||||
|
|
||||||
|
'@vitest/spy@3.1.3':
|
||||||
|
dependencies:
|
||||||
|
tinyspy: 3.0.2
|
||||||
|
|
||||||
|
'@vitest/utils@3.1.3':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/pretty-format': 3.1.3
|
||||||
|
loupe: 3.1.3
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
|
||||||
'@xmldom/xmldom@0.7.13': {}
|
'@xmldom/xmldom@0.7.13': {}
|
||||||
|
|
||||||
'@xmldom/xmldom@0.8.10': {}
|
'@xmldom/xmldom@0.8.10': {}
|
||||||
@@ -19100,13 +19264,13 @@ snapshots:
|
|||||||
|
|
||||||
agent-base@6.0.2:
|
agent-base@6.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.7
|
debug: 4.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
agent-base@7.1.1:
|
agent-base@7.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.7
|
debug: 4.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -19276,6 +19440,8 @@ snapshots:
|
|||||||
|
|
||||||
asap@2.0.6: {}
|
asap@2.0.6: {}
|
||||||
|
|
||||||
|
assertion-error@2.0.1: {}
|
||||||
|
|
||||||
ast-types@0.15.2:
|
ast-types@0.15.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.7.0
|
tslib: 2.7.0
|
||||||
@@ -19791,6 +19957,14 @@ snapshots:
|
|||||||
|
|
||||||
ccount@2.0.1: {}
|
ccount@2.0.1: {}
|
||||||
|
|
||||||
|
chai@5.2.0:
|
||||||
|
dependencies:
|
||||||
|
assertion-error: 2.0.1
|
||||||
|
check-error: 2.1.1
|
||||||
|
deep-eql: 5.0.2
|
||||||
|
loupe: 3.1.3
|
||||||
|
pathval: 2.0.0
|
||||||
|
|
||||||
chalk@2.4.2:
|
chalk@2.4.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-styles: 3.2.1
|
ansi-styles: 3.2.1
|
||||||
@@ -19822,6 +19996,8 @@ snapshots:
|
|||||||
|
|
||||||
charenc@0.0.2: {}
|
charenc@0.0.2: {}
|
||||||
|
|
||||||
|
check-error@2.1.1: {}
|
||||||
|
|
||||||
cheerio-select@2.1.0:
|
cheerio-select@2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
boolbase: 1.0.0
|
boolbase: 1.0.0
|
||||||
@@ -20437,6 +20613,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
character-entities: 2.0.2
|
character-entities: 2.0.2
|
||||||
|
|
||||||
|
deep-eql@5.0.2: {}
|
||||||
|
|
||||||
deep-extend@0.6.0: {}
|
deep-extend@0.6.0: {}
|
||||||
|
|
||||||
deepmerge@4.3.1: {}
|
deepmerge@4.3.1: {}
|
||||||
@@ -21036,6 +21214,8 @@ snapshots:
|
|||||||
signal-exit: 3.0.7
|
signal-exit: 3.0.7
|
||||||
strip-final-newline: 2.0.0
|
strip-final-newline: 2.0.0
|
||||||
|
|
||||||
|
expect-type@1.2.1: {}
|
||||||
|
|
||||||
expo-application@5.3.1(expo@50.0.7(@babel/core@7.24.5)(@react-native/babel-preset@0.73.21(@babel/core@7.24.5)(@babel/preset-env@7.23.9(@babel/core@7.24.5)))):
|
expo-application@5.3.1(expo@50.0.7(@babel/core@7.24.5)(@react-native/babel-preset@0.73.21(@babel/core@7.24.5)(@babel/preset-env@7.23.9(@babel/core@7.24.5)))):
|
||||||
dependencies:
|
dependencies:
|
||||||
expo: 50.0.7(@babel/core@7.24.5)(@react-native/babel-preset@0.73.21(@babel/core@7.24.5)(@babel/preset-env@7.23.9(@babel/core@7.24.5)))
|
expo: 50.0.7(@babel/core@7.24.5)(@react-native/babel-preset@0.73.21(@babel/core@7.24.5)(@babel/preset-env@7.23.9(@babel/core@7.24.5)))
|
||||||
@@ -22090,7 +22270,7 @@ snapshots:
|
|||||||
https-proxy-agent@7.0.5:
|
https-proxy-agent@7.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.1
|
agent-base: 7.1.1
|
||||||
debug: 4.3.7
|
debug: 4.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -22836,6 +23016,8 @@ snapshots:
|
|||||||
|
|
||||||
lottie-web@5.12.2: {}
|
lottie-web@5.12.2: {}
|
||||||
|
|
||||||
|
loupe@3.1.3: {}
|
||||||
|
|
||||||
lowlight@1.20.0:
|
lowlight@1.20.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
fault: 1.0.4
|
fault: 1.0.4
|
||||||
@@ -23563,7 +23745,7 @@ snapshots:
|
|||||||
micromark@4.0.0:
|
micromark@4.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/debug': 4.1.12
|
'@types/debug': 4.1.12
|
||||||
debug: 4.3.7
|
debug: 4.4.0
|
||||||
decode-named-character-reference: 1.0.2
|
decode-named-character-reference: 1.0.2
|
||||||
devlop: 1.1.0
|
devlop: 1.1.0
|
||||||
micromark-core-commonmark: 2.0.1
|
micromark-core-commonmark: 2.0.1
|
||||||
@@ -24257,6 +24439,10 @@ snapshots:
|
|||||||
|
|
||||||
path-type@4.0.0: {}
|
path-type@4.0.0: {}
|
||||||
|
|
||||||
|
pathe@2.0.3: {}
|
||||||
|
|
||||||
|
pathval@2.0.0: {}
|
||||||
|
|
||||||
peberminta@0.9.0: {}
|
peberminta@0.9.0: {}
|
||||||
|
|
||||||
peek-stream@1.1.3:
|
peek-stream@1.1.3:
|
||||||
@@ -25800,6 +25986,8 @@ snapshots:
|
|||||||
get-intrinsic: 1.2.4
|
get-intrinsic: 1.2.4
|
||||||
object-inspect: 1.13.1
|
object-inspect: 1.13.1
|
||||||
|
|
||||||
|
siginfo@2.0.0: {}
|
||||||
|
|
||||||
signal-exit@3.0.7: {}
|
signal-exit@3.0.7: {}
|
||||||
|
|
||||||
signal-exit@4.1.0: {}
|
signal-exit@4.1.0: {}
|
||||||
@@ -25921,6 +26109,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
escape-string-regexp: 2.0.0
|
escape-string-regexp: 2.0.0
|
||||||
|
|
||||||
|
stackback@0.0.2: {}
|
||||||
|
|
||||||
stackframe@1.3.4: {}
|
stackframe@1.3.4: {}
|
||||||
|
|
||||||
stacktrace-parser@0.1.10:
|
stacktrace-parser@0.1.10:
|
||||||
@@ -25938,6 +26128,8 @@ snapshots:
|
|||||||
|
|
||||||
statuses@2.0.1: {}
|
statuses@2.0.1: {}
|
||||||
|
|
||||||
|
std-env@3.9.0: {}
|
||||||
|
|
||||||
stop-iteration-iterator@1.0.0:
|
stop-iteration-iterator@1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
internal-slot: 1.0.7
|
internal-slot: 1.0.7
|
||||||
@@ -26328,6 +26520,8 @@ snapshots:
|
|||||||
|
|
||||||
tiny-lru@11.2.11: {}
|
tiny-lru@11.2.11: {}
|
||||||
|
|
||||||
|
tinybench@2.9.0: {}
|
||||||
|
|
||||||
tinyexec@0.3.2: {}
|
tinyexec@0.3.2: {}
|
||||||
|
|
||||||
tinyglobby@0.2.13:
|
tinyglobby@0.2.13:
|
||||||
@@ -26335,6 +26529,12 @@ snapshots:
|
|||||||
fdir: 6.4.4(picomatch@4.0.2)
|
fdir: 6.4.4(picomatch@4.0.2)
|
||||||
picomatch: 4.0.2
|
picomatch: 4.0.2
|
||||||
|
|
||||||
|
tinypool@1.0.2: {}
|
||||||
|
|
||||||
|
tinyrainbow@2.0.0: {}
|
||||||
|
|
||||||
|
tinyspy@3.0.2: {}
|
||||||
|
|
||||||
tmp@0.0.33:
|
tmp@0.0.33:
|
||||||
dependencies:
|
dependencies:
|
||||||
os-tmpdir: 1.0.2
|
os-tmpdir: 1.0.2
|
||||||
@@ -26799,6 +26999,27 @@ snapshots:
|
|||||||
d3-time: 3.1.0
|
d3-time: 3.1.0
|
||||||
d3-timer: 3.0.1
|
d3-timer: 3.0.1
|
||||||
|
|
||||||
|
vite-node@3.1.3(@types/node@20.14.8)(jiti@2.4.1)(terser@5.27.1):
|
||||||
|
dependencies:
|
||||||
|
cac: 6.7.14
|
||||||
|
debug: 4.4.0
|
||||||
|
es-module-lexer: 1.7.0
|
||||||
|
pathe: 2.0.3
|
||||||
|
vite: 6.3.3(@types/node@20.14.8)(jiti@2.4.1)(terser@5.27.1)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/node'
|
||||||
|
- jiti
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- sass
|
||||||
|
- sass-embedded
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
- tsx
|
||||||
|
- yaml
|
||||||
|
|
||||||
vite@6.3.3(@types/node@20.14.8)(jiti@2.4.1)(terser@5.27.1):
|
vite@6.3.3(@types/node@20.14.8)(jiti@2.4.1)(terser@5.27.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.25.3
|
esbuild: 0.25.3
|
||||||
@@ -26817,6 +27038,46 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vite: 6.3.3(@types/node@20.14.8)(jiti@2.4.1)(terser@5.27.1)
|
vite: 6.3.3(@types/node@20.14.8)(jiti@2.4.1)(terser@5.27.1)
|
||||||
|
|
||||||
|
vitest@3.1.3(@types/debug@4.1.12)(@types/node@20.14.8)(jiti@2.4.1)(terser@5.27.1):
|
||||||
|
dependencies:
|
||||||
|
'@vitest/expect': 3.1.3
|
||||||
|
'@vitest/mocker': 3.1.3(vite@6.3.3(@types/node@20.14.8)(jiti@2.4.1)(terser@5.27.1))
|
||||||
|
'@vitest/pretty-format': 3.1.3
|
||||||
|
'@vitest/runner': 3.1.3
|
||||||
|
'@vitest/snapshot': 3.1.3
|
||||||
|
'@vitest/spy': 3.1.3
|
||||||
|
'@vitest/utils': 3.1.3
|
||||||
|
chai: 5.2.0
|
||||||
|
debug: 4.4.0
|
||||||
|
expect-type: 1.2.1
|
||||||
|
magic-string: 0.30.17
|
||||||
|
pathe: 2.0.3
|
||||||
|
std-env: 3.9.0
|
||||||
|
tinybench: 2.9.0
|
||||||
|
tinyexec: 0.3.2
|
||||||
|
tinyglobby: 0.2.13
|
||||||
|
tinypool: 1.0.2
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
vite: 6.3.3(@types/node@20.14.8)(jiti@2.4.1)(terser@5.27.1)
|
||||||
|
vite-node: 3.1.3(@types/node@20.14.8)(jiti@2.4.1)(terser@5.27.1)
|
||||||
|
why-is-node-running: 2.3.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/debug': 4.1.12
|
||||||
|
'@types/node': 20.14.8
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- jiti
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- msw
|
||||||
|
- sass
|
||||||
|
- sass-embedded
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
- tsx
|
||||||
|
- yaml
|
||||||
|
|
||||||
vlq@1.0.1: {}
|
vlq@1.0.1: {}
|
||||||
|
|
||||||
walker@1.0.8:
|
walker@1.0.8:
|
||||||
@@ -26892,6 +27153,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
isexe: 2.0.0
|
isexe: 2.0.0
|
||||||
|
|
||||||
|
why-is-node-running@2.3.0:
|
||||||
|
dependencies:
|
||||||
|
siginfo: 2.0.0
|
||||||
|
stackback: 0.0.2
|
||||||
|
|
||||||
wide-align@1.1.5:
|
wide-align@1.1.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
|
|||||||
27
vitest.shared.ts
Normal file
27
vitest.shared.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import * as path from 'node:path';
|
||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export const getSharedVitestConfig = ({
|
||||||
|
__dirname: dirname,
|
||||||
|
}: { __dirname: string }) => {
|
||||||
|
return defineConfig({
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(dirname, 'src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
env: {
|
||||||
|
// Not used, just so prisma is happy
|
||||||
|
DATABASE_URL: 'postgresql://u:p@127.0.0.1:5432/db',
|
||||||
|
},
|
||||||
|
include: ['**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||||
|
browser: {
|
||||||
|
name: 'chromium',
|
||||||
|
provider: 'playwright',
|
||||||
|
headless: true,
|
||||||
|
},
|
||||||
|
fakeTimers: { toFake: undefined },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
1
vitest.workspace.ts
Normal file
1
vitest.workspace.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default ['packages/*', 'apps/*'];
|
||||||
Reference in New Issue
Block a user