Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module.exports = {
files: ['*.test.ts'],
rules: {
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-empty-function': 'warn',
},
},
],
Expand Down
24 changes: 13 additions & 11 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:

jobs:
check_release:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.check.outputs.should_run }}
steps:
Expand All @@ -24,24 +24,26 @@ jobs:
core.setOutput('should_run', shouldRun);

test_build_deploy:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
needs: check_release
if: needs.check_release.outputs.should_run == 'true'
steps:
- uses: actions/checkout@v5
- name: Use Node.js
uses: actions/setup-node@v5
- name: Checkout
uses: actions/checkout@v5

- name: Bun Setup
uses: oven-sh/setup-bun@v2
with:
node-version-file: '.nvmrc'
bun-version: latest

- name: 'Install dependencies'
run: npm ci
- name: 'Install'
run: bun install

- name: 'Build for Staging'
run: npm run build:staging
run: bun run build:staging

- name: 'Build for Production'
run: npm run build:prod
run: bun run build:prod

- name: 'Authenticate to Google Cloud'
uses: 'google-github-actions/auth@v1'
Expand All @@ -57,7 +59,7 @@ jobs:

- name: 'Get SDK Version'
id: get_version
run: echo "SDK_VERSION=$(node -p "require('./package.json').config.sdkVersion")" >> $GITHUB_OUTPUT
run: echo "SDK_VERSION=$(bun -e "console.log(require('./package.json').config.sdkVersion)")" >> $GITHUB_OUTPUT

- name: 'Create turbine pr'
run: ./build/scripts/turbine.sh
Expand Down
28 changes: 17 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,30 @@ on:

jobs:
test:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v5
- name: Checkout
uses: actions/checkout@v5

- name: Use Node.js
- name: Setup Node # Since Vitest uses Node apis
uses: actions/setup-node@v5
with:
node-version-file: '.nvmrc'

- name: '[Setup] Install dependencies'
run: npm ci
- name: Bun Setup
uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: 'Install'
run: bun install

- name: 'Check build size'
run: npm run build:prod
- name: 'Check Build'
run: bun run build:prod

- name: '[Test] Run linters'
run: npm run lint
- name: 'Lint'
run: bun run lint

- name: '[Test] Run all tests'
run: npm run test
- name: 'Test'
run: bun run test
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
22.13.1
22.22.0
120 changes: 120 additions & 0 deletions TESTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Testing Guidelines

## Basic Test

Here is a basic test file template.

```ts
import { TestEnvironment } from '../../support/environment/TestEnvironment';

// mock an entire file
vi.mock('../../../src/MyFile');

describe('My tests', () => {
beforeEach(() => {
TestEnvironment.initialize();
});

afterEach(() => {
vi.resetAllMocks();
});

test('This is a test description', () => {});
});
```

## Extension

You can use the [ms-playwright](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright) extension to run individual tests by running a "Play" button next to the test case.

You can even right click the "Play" and click "Debug Test" to open a debugger. You can either set a breakpoint or put a `debugger` statement in the src file.

## Common Usages

### Suppress Internal Logging

Your test may result in an error being printed but the test still succeeds. To suppress logs, you can mock the entire Log file.

```ts
// suppress all internal logging
vi.mock('../../../src/shared/libraries/Log');
```

### API

#### `nock` (Not Recommended)

Can leverage our custom nock helper to mock a response for some fetch call. E.g,

```ts
import { nock } from '__test__/support/mocks/nock';

nock({}, 400);

await someFetchCall();
```

#### `msw`

Mocks all HTTP requests by leveraging msw. If omitted, the status defaults to 200.

```ts
import { server } from '__test__/support/mocks/server';
import { http, HttpResponse } from 'msw';

server.use(
http.get('https://api.onesignal.com/notifications', () =>
HttpResponse.json({ result: {}, status: 200 }),
),
);

// or

server.use(
http.get('**/v1/notifications', () =>
HttpResponse.json({ result: {}, status: 200 }),
),
);
```

## Fake Timers and IndexedDB

`fake-indexeddb` does not work correctly when `vi.useFakeTimers()` is called without restrictions. Faking all timers interferes with IndexedDB's internal async operations, causing tests to hang or fail.

### Rules

1. **Never use `vi.useFakeTimers()` without a `toFake` list** in tests that touch IndexedDB (directly or indirectly through code that reads/writes to the database).

2. **If you only need to fake specific timer APIs**, pass a `toFake` array with only what you need:

```ts
// Only fake setInterval
vi.useFakeTimers({ toFake: ['setInterval'] });

// Only fake Date
vi.useFakeTimers({ toFake: ['Date'] });

// Only fake setTimeout/clearTimeout
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] });
```

3. **If you need to avoid time-based delays**, use the `mockDelay` helper instead of fake timers. It mocks the `delay` function from `src/shared/helpers/general` to resolve immediately:

```ts
import { mockDelay } from '__test__/support/helpers/setup';

// Must be called at the top level of the test file (alongside other vi.mock calls)
mockDelay();
```

You can then assert on the delay spy to verify the correct delay duration was requested:

```ts
import { delay as delaySpy } from 'src/shared/helpers/general';

expect(delaySpy).toHaveBeenCalledWith(30000);
```

### Global Setup

`setupTests.ts` mocks all operation repo timing constants to small values (10ms), so tests using real timers will not have long waits. This means most tests can run without fake timers at all.
77 changes: 0 additions & 77 deletions __test__/README.md

This file was deleted.

45 changes: 33 additions & 12 deletions __test__/support/helpers/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ import type {
} from 'src/shared/database/types';
import { RawPushSubscription } from 'src/shared/models/RawPushSubscription';

/**
* Mocks `delay` from `src/shared/helpers/general` to resolve immediately.
* Must be called at the top level of a test file (alongside other vi.mock calls).
*/
export const mockDelay = () => {
vi.mock('src/shared/helpers/general', async (importOriginal) => {
const mod =
await importOriginal<typeof import('src/shared/helpers/general')>();
return { ...mod, delay: vi.fn(() => Promise.resolve()) };
});
};

export const setIsPushEnabled = async (isPushEnabled: boolean) => {
await db.put('Options', { key: 'isPushEnabled', value: isPushEnabled });
};
Expand All @@ -23,10 +35,13 @@ export const getIdentityItem = async (
condition: (identity: IdentitySchema) => boolean = () => true,
) => {
let identity: IdentitySchema | undefined;
await vi.waitUntil(async () => {
identity = (await db.getAll('identity'))?.[0];
return identity && condition(identity);
});
await vi.waitUntil(
async () => {
identity = (await db.getAll('identity'))?.[0];
return identity && condition(identity);
},
{ interval: 1 },
);
return identity;
};

Expand All @@ -37,10 +52,13 @@ export const getPropertiesItem = async (
condition: (properties: PropertiesSchema) => boolean = () => true,
) => {
let properties: PropertiesSchema | undefined;
await vi.waitUntil(async () => {
properties = (await db.getAll('properties'))?.[0];
return properties && condition(properties);
});
await vi.waitUntil(
async () => {
properties = (await db.getAll('properties'))?.[0];
return properties && condition(properties);
},
{ interval: 1 },
);
return properties;
};

Expand All @@ -49,10 +67,13 @@ export const getPropertiesItem = async (
*/
export const getDbSubscriptions = async (length: number) => {
let subscriptions: SubscriptionSchema[] = [];
await vi.waitUntil(async () => {
subscriptions = await db.getAll('subscriptions');
return subscriptions.length === length;
});
await vi.waitUntil(
async () => {
subscriptions = await db.getAll('subscriptions');
return subscriptions.length === length;
},
{ interval: 1 },
);
return subscriptions;
};

Expand Down
2 changes: 0 additions & 2 deletions __test__/unit/pushSubscription/nativePermissionChange.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ const triggerNotificationSpy = vi.spyOn(
'triggerNotificationPermissionChanged',
);

vi.useFakeTimers();

describe('Notification Types are set correctly on subscription change', () => {
beforeEach(() => {
TestEnvironment.initialize();
Expand Down
Loading
Loading