From 8bb648991893c7fa1267d0b295c8a7dd7b9c2c2f Mon Sep 17 00:00:00 2001 From: Matsu Date: Thu, 9 Jul 2026 09:29:24 +0300 Subject: [PATCH] chore: Remove remaining Jest references (no-changelog) (#33412) Co-authored-by: Claude Opus 4.8 (1M context) Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> --- .github/workflows/test-unit-reusable.yml | 3 +- .../workspace/sandbox/daytona-sandbox.test.ts | 2 +- .../discovery-subgraph.integration.test.ts | 2 +- .../src/http/fake-outbound-http.ts | 9 +++--- packages/@n8n/backend-test-utils/package.json | 2 +- packages/@n8n/benchmark/package.json | 2 +- packages/@n8n/constants/package.json | 2 +- packages/@n8n/db/vite.config.ts | 4 +-- packages/@n8n/errors/package.json | 2 +- .../rules/no-error-instance-in-to-throw.ts | 2 +- .../rules/no-forbidden-lifecycle-scripts.md | 2 +- packages/@n8n/extension-sdk/package.json | 2 +- .../@n8n/instance-ai/src/parsers/index.ts | 2 +- .../require-resolver-global-modules.test.ts | 2 +- .../@n8n/vitest-config/coverage-excludes.ts | 3 -- .../src/validation/test-schema-setup.ts | 4 +-- .../cli/src/__tests__/workflow-runner.test.ts | 2 +- .../__tests__/task-broker.service.test.ts | 2 +- .../repositories/workflow.repository.test.ts | 2 +- packages/cli/test/integration/shared/types.ts | 2 +- .../integration/shared/utils/test-server.ts | 2 +- scripts/grind.mjs | 30 +++++++------------ scripts/mutation-health/build-matrix.test.mjs | 10 ++----- scripts/mutation-health/pick-next.mjs | 8 ++--- scripts/mutation-health/pick-next.test.mjs | 5 +--- 25 files changed, 43 insertions(+), 65 deletions(-) diff --git a/.github/workflows/test-unit-reusable.yml b/.github/workflows/test-unit-reusable.yml index 94fc419905d..d075300cc84 100644 --- a/.github/workflows/test-unit-reusable.yml +++ b/.github/workflows/test-unit-reusable.yml @@ -29,8 +29,7 @@ on: description: | Newline-separated list of CHANGED_FILES from ci-filter. Passed to per-package `janitor scope` invocations so test runners can filter - to actually-changed source files via jest --findRelatedTests or - vitest related. + to actually-changed source files via vitest related. required: false default: '' type: string diff --git a/packages/@n8n/agents/src/__tests__/workspace/sandbox/daytona-sandbox.test.ts b/packages/@n8n/agents/src/__tests__/workspace/sandbox/daytona-sandbox.test.ts index c51a147283b..a8f496741a0 100644 --- a/packages/@n8n/agents/src/__tests__/workspace/sandbox/daytona-sandbox.test.ts +++ b/packages/@n8n/agents/src/__tests__/workspace/sandbox/daytona-sandbox.test.ts @@ -1,7 +1,7 @@ import type { Mock } from 'vitest'; // Mock @daytona/sdk so we can drive sandbox creation, token refresh, and -// sandbox refetch behavior from Jest without touching the network. +// sandbox refetch behavior from Vitest without touching the network. interface MockSandbox { id: string; diff --git a/packages/@n8n/ai-workflow-builder.ee/src/subgraphs/test/integration/discovery-subgraph.integration.test.ts b/packages/@n8n/ai-workflow-builder.ee/src/subgraphs/test/integration/discovery-subgraph.integration.test.ts index 6cff1c892d1..1fa0a21e02c 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/subgraphs/test/integration/discovery-subgraph.integration.test.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/subgraphs/test/integration/discovery-subgraph.integration.test.ts @@ -185,7 +185,7 @@ describe('Discovery Subgraph - Integration Tests', () => { vi.setConfig({ testTimeout: 1800000 }); // 30 minutes beforeAll(async () => { - // Override console.log to use process.stdout directly, bypassing Jest's + // Override console.log to use process.stdout directly, bypassing Vitest's // verbose wrapper that adds stack traces to every log line vi.spyOn(console, 'log').mockImplementation((...args: unknown[]) => { process.stdout.write(args.map(String).join(' ') + '\n'); diff --git a/packages/@n8n/backend-network/src/http/fake-outbound-http.ts b/packages/@n8n/backend-network/src/http/fake-outbound-http.ts index cb899546d3a..10fd0647767 100644 --- a/packages/@n8n/backend-network/src/http/fake-outbound-http.ts +++ b/packages/@n8n/backend-network/src/http/fake-outbound-http.ts @@ -106,10 +106,9 @@ function respondWith(routes: Route[]) { type AnyFn = (...args: any[]) => any; /** - * Test-framework mock factory — pass `jest.fn` or `vi.fn`. Injected so this - * helper stays framework-neutral: `@n8n/backend-network` runs on vitest while - * consumers such as `packages/cli` run on jest, and `expect(fn).toHaveBeenCalled…` - * assertions need a real mock from the consumer's own framework. + * Test-framework mock factory — pass `vi.fn`. Injected so this helper stays + * framework-neutral and `expect(fn).toHaveBeenCalled…` assertions get a real + * mock from the consumer's own test framework. */ export type MockFnFactory = ( impl: T, @@ -121,7 +120,7 @@ export type MockFnFactory = ( * `headers` merged), so assertions see exactly what the real client would send. * * @param routes responses to serve, matched and consumed in order. - * @param fn the consumer's mock factory (`jest.fn` / `vi.fn`). + * @param fn the consumer's mock factory (`vi.fn`). */ export function createFakeOutboundHttp(routes: Route[], fn: MockFnFactory) { const stub = respondWith(routes); diff --git a/packages/@n8n/backend-test-utils/package.json b/packages/@n8n/backend-test-utils/package.json index 532f17429d0..e407a51e466 100644 --- a/packages/@n8n/backend-test-utils/package.json +++ b/packages/@n8n/backend-test-utils/package.json @@ -12,7 +12,7 @@ "lint": "eslint . --quiet", "lint:fix": "eslint . --fix", "watch": "tsc -p tsconfig.build.json --watch", - "test": "echo \"WARNING: no test specified\" && exit 0", + "test": "node -e \"process.exit(0)\"", "test:dev": "echo \"WARNING: no test specified\" && exit 0" }, "main": "dist/index.js", diff --git a/packages/@n8n/benchmark/package.json b/packages/@n8n/benchmark/package.json index 77591323874..607a8d60e0a 100644 --- a/packages/@n8n/benchmark/package.json +++ b/packages/@n8n/benchmark/package.json @@ -12,7 +12,7 @@ "lint": "eslint . --quiet", "lint:fix": "eslint . --fix", "start": "./bin/n8n-benchmark", - "test": "echo \"WARNING: no test specified\" && exit 0", + "test": "node -e \"process.exit(0)\"", "typecheck": "tsc --noEmit", "benchmark": "zx scripts/run.mjs", "benchmark-in-cloud": "pnpm benchmark --env cloud", diff --git a/packages/@n8n/constants/package.json b/packages/@n8n/constants/package.json index af3d7464c79..8c04c3e9917 100644 --- a/packages/@n8n/constants/package.json +++ b/packages/@n8n/constants/package.json @@ -12,7 +12,7 @@ "lint": "eslint . --quiet", "lint:fix": "eslint . --fix", "watch": "tsc -p tsconfig.build.json --watch", - "test": "jest --passWithNoTests" + "test": "node -e \"process.exit(0)\"" }, "main": "dist/index.js", "module": "src/index.ts", diff --git a/packages/@n8n/db/vite.config.ts b/packages/@n8n/db/vite.config.ts index f9ad0474b94..7ceed4b8fe5 100644 --- a/packages/@n8n/db/vite.config.ts +++ b/packages/@n8n/db/vite.config.ts @@ -16,8 +16,8 @@ import { configDefaults } from 'vitest/config'; * only `tsc` with cross-file type information collapses the union to `String`. Vite's * oxc transform — and SWC — emit `Object` instead, which TypeORM rejects at * `DataSource.initialize()`. Single-file `transpileModule` also emits `Object` because - * it can't resolve the imported alias. A full Program is required, which mirrors the - * old jest config that set `isolatedModules: false` for exactly this reason. + * it can't resolve the imported alias. A full Program is required — the same + * reason a non-isolated-modules TypeScript compile is needed here. * * Scoping to `src/entities/**` keeps the cost contained: only the ~50 entity files pay * the tsc price (and the Program is rooted there), while DI `@Service` constructor diff --git a/packages/@n8n/errors/package.json b/packages/@n8n/errors/package.json index 64cca3ebf8f..cb647b900e5 100644 --- a/packages/@n8n/errors/package.json +++ b/packages/@n8n/errors/package.json @@ -12,7 +12,7 @@ "lint": "eslint . --quiet", "lint:fix": "eslint . --fix", "watch": "tsc -p tsconfig.build.json --watch", - "test": "echo \"WARNING: no test specified\" && exit 0", + "test": "node -e \"process.exit(0)\"", "test:dev": "echo \"WARNING: no test specified\" && exit 0" }, "main": "dist/index.js", diff --git a/packages/@n8n/eslint-config/src/rules/no-error-instance-in-to-throw.ts b/packages/@n8n/eslint-config/src/rules/no-error-instance-in-to-throw.ts index f8ed4d05313..ea299d82a58 100644 --- a/packages/@n8n/eslint-config/src/rules/no-error-instance-in-to-throw.ts +++ b/packages/@n8n/eslint-config/src/rules/no-error-instance-in-to-throw.ts @@ -5,7 +5,7 @@ export const NoErrorInstanceInToThrowRule = ESLintUtils.RuleCreator.withoutDocs( type: 'problem', docs: { description: - 'Disallow passing error instances to `.toThrow()` and `.toThrowError`. Jest compares by reference, not message, making assertions flaky. Pass the error class and message string separately instead.', + 'Disallow passing error instances to `.toThrow()` and `.toThrowError`. Passing an instance makes the assertion ambiguous and flaky. Pass the error class and message string separately instead.', }, messages: { noErrorInstance: diff --git a/packages/@n8n/eslint-plugin-community-nodes/docs/rules/no-forbidden-lifecycle-scripts.md b/packages/@n8n/eslint-plugin-community-nodes/docs/rules/no-forbidden-lifecycle-scripts.md index 4014eb9c36e..3735456d7c7 100644 --- a/packages/@n8n/eslint-plugin-community-nodes/docs/rules/no-forbidden-lifecycle-scripts.md +++ b/packages/@n8n/eslint-plugin-community-nodes/docs/rules/no-forbidden-lifecycle-scripts.md @@ -40,7 +40,7 @@ n8n community nodes are distributed as pre-built npm packages. Unlike regular np "name": "n8n-nodes-example", "scripts": { "build": "tsc", - "test": "jest" + "test": "vitest run" } } ``` diff --git a/packages/@n8n/extension-sdk/package.json b/packages/@n8n/extension-sdk/package.json index 9ec33b974c0..6936f1bbdf1 100644 --- a/packages/@n8n/extension-sdk/package.json +++ b/packages/@n8n/extension-sdk/package.json @@ -39,7 +39,7 @@ "build:unchecked": "pnpm run build", "create-json-schema": "tsx scripts/create-json-schema.ts", "preview": "vite preview", - "test": "node -e \"process.exit(0);\"" + "test": "node -e \"process.exit(0)\"" }, "peerDependencies": { "vue": "catalog:frontend", diff --git a/packages/@n8n/instance-ai/src/parsers/index.ts b/packages/@n8n/instance-ai/src/parsers/index.ts index bf3f04e98da..bf6fc8e7e35 100644 --- a/packages/@n8n/instance-ai/src/parsers/index.ts +++ b/packages/@n8n/instance-ai/src/parsers/index.ts @@ -3,7 +3,7 @@ * * This entry point intentionally avoids importing anything from `../agent` * or other native agent-tainted modules so it remains safe to consume from - * Jest CJS test environments. + * downstream test environments. */ export { diff --git a/packages/@n8n/task-runner/src/js-task-runner/__tests__/require-resolver-global-modules.test.ts b/packages/@n8n/task-runner/src/js-task-runner/__tests__/require-resolver-global-modules.test.ts index 6df0b629617..65fb8463efe 100644 --- a/packages/@n8n/task-runner/src/js-task-runner/__tests__/require-resolver-global-modules.test.ts +++ b/packages/@n8n/task-runner/src/js-task-runner/__tests__/require-resolver-global-modules.test.ts @@ -13,7 +13,7 @@ import * as path from 'node:path'; * 2. NODE_PATH must not be clobbered by load-nodes-and-credentials.ts * 3. Module._initPaths() must be called to pick up NODE_PATH at runtime * - * Tests use child processes because Jest intercepts require() with its own + * Tests use child processes because Vitest intercepts require() with its own * resolver which doesn't respect NODE_PATH changes. * * IMPORTANT: Scripts are placed in a separate directory from the installed diff --git a/packages/@n8n/vitest-config/coverage-excludes.ts b/packages/@n8n/vitest-config/coverage-excludes.ts index 79ceebad037..83e56aea181 100644 --- a/packages/@n8n/vitest-config/coverage-excludes.ts +++ b/packages/@n8n/vitest-config/coverage-excludes.ts @@ -3,9 +3,6 @@ * only auto-excludes the test files it runs, so type decls, mocks and test * helpers otherwise land in the denominator at 0%. Spread these onto * `coverageConfigDefaults.exclude` in each config. - * - * Mirrors the jest equivalent at the repo root (jest.coverage-excludes.js) — - * same intent, plain globs here vs `!`-negated globs there. Keep them in sync. */ export const coverageExcludes = [ '**/*.spec.ts', diff --git a/packages/@n8n/workflow-sdk/src/validation/test-schema-setup.ts b/packages/@n8n/workflow-sdk/src/validation/test-schema-setup.ts index 6f4ae1252b5..23166858466 100644 --- a/packages/@n8n/workflow-sdk/src/validation/test-schema-setup.ts +++ b/packages/@n8n/workflow-sdk/src/validation/test-schema-setup.ts @@ -17,10 +17,10 @@ import { generateNodeDefinitions } from '../generate-types/generate-node-defs-cl // Use a worker-specific directory to prevent race conditions when multiple test // workers run schema-using tests in parallel (they would otherwise concurrently // delete and regenerate schemas into the same shared temp directory). -// Vitest exposes the per-worker id as VITEST_POOL_ID; Jest used JEST_WORKER_ID. +// Vitest exposes the per-worker id as VITEST_POOL_ID. // Without this, every parallel worker collapses onto the same `-0` directory and // one worker's rmSync wipes schemas another worker just generated. -const WORKER_ID = process.env.VITEST_POOL_ID ?? process.env.JEST_WORKER_ID ?? '0'; +const WORKER_ID = process.env.VITEST_POOL_ID ?? '0'; const SCHEMA_TEST_DIR = path.join(os.tmpdir(), `n8n-schema-tests-${WORKER_ID}`); const STAMP_FILE = path.join(SCHEMA_TEST_DIR, '.generator-hash'); diff --git a/packages/cli/src/__tests__/workflow-runner.test.ts b/packages/cli/src/__tests__/workflow-runner.test.ts index 5f2e5a10614..c69240c3b2e 100644 --- a/packages/cli/src/__tests__/workflow-runner.test.ts +++ b/packages/cli/src/__tests__/workflow-runner.test.ts @@ -425,7 +425,7 @@ describe('enqueueExecution', () => { const error = new Error('stop for test purposes'); // mock a rejection to stop execution flow before we create the PCancelable promise, - // so that Jest does not move on to tear down the suite until the PCancelable settles + // so that Vitest does not move on to tear down the suite until the PCancelable settles addJob.mockRejectedValueOnce(error); // @ts-expect-error Private method diff --git a/packages/cli/src/task-runners/task-broker/__tests__/task-broker.service.test.ts b/packages/cli/src/task-runners/task-broker/__tests__/task-broker.service.test.ts index 84c53171039..b7fa53c8a73 100644 --- a/packages/cli/src/task-runners/task-broker/__tests__/task-broker.service.test.ts +++ b/packages/cli/src/task-runners/task-broker/__tests__/task-broker.service.test.ts @@ -262,7 +262,7 @@ describe('TaskBroker', () => { validUntil: createValidUntil(1000), }; - vi.spyOn(taskBroker, 'acceptOffer').mockResolvedValue(); // allow Jest to exit cleanly + vi.spyOn(taskBroker, 'acceptOffer').mockResolvedValue(); // allow Vitest to exit cleanly taskBroker.taskOffered(offer); diff --git a/packages/cli/test/integration/database/repositories/workflow.repository.test.ts b/packages/cli/test/integration/database/repositories/workflow.repository.test.ts index 9e2227c5a90..e685d205c44 100644 --- a/packages/cli/test/integration/database/repositories/workflow.repository.test.ts +++ b/packages/cli/test/integration/database/repositories/workflow.repository.test.ts @@ -41,7 +41,7 @@ function expectWorkflowsMatch( const oldSorted = [...oldWorkflows].sort((a, b) => a.id.localeCompare(b.id)); const newSorted = [...newWorkflows].sort((a, b) => a.id.localeCompare(b.id)); - // Jest's toEqual does deep recursive comparison of all fields + // Vitest's toEqual does deep recursive comparison of all fields expect(newSorted).toEqual(oldSorted); } diff --git a/packages/cli/test/integration/shared/types.ts b/packages/cli/test/integration/shared/types.ts index 03962437cca..8160ec3f741 100644 --- a/packages/cli/test/integration/shared/types.ts +++ b/packages/cli/test/integration/shared/types.ts @@ -73,7 +73,7 @@ export interface SetupProps { enabledFeatures?: BooleanLicenseFeature[]; quotas?: Partial<{ [K in NumericLicenseFeature]: number }>; modules?: ModuleName[]; - /** Override the default Jest timeout (ms) for the shared `beforeAll` setup hook. */ + /** Override the default test timeout (ms) for the shared `beforeAll` setup hook. */ setupTimeout?: number; } diff --git a/packages/cli/test/integration/shared/utils/test-server.ts b/packages/cli/test/integration/shared/utils/test-server.ts index 29ef7ac9273..5d055199403 100644 --- a/packages/cli/test/integration/shared/utils/test-server.ts +++ b/packages/cli/test/integration/shared/utils/test-server.ts @@ -379,7 +379,7 @@ export const setupTestServer = ({ // Close the HTTP server first so any in-flight requests can't reach the // DI container after testDb.terminate() resets it. Await the close so // pending handlers drain before the next file's beforeAll runs in - // persistent Jest workers — otherwise stale handlers call + // persistent Vitest workers — otherwise stale handlers call // Container.get(Logger), construct a fresh Logger, and trip Vitest's // "environment torn down" guard when winston is imported. // Skip when the server never started listening (some suites bail in diff --git a/scripts/grind.mjs b/scripts/grind.mjs index d60585166cb..5ce62cf848e 100644 --- a/scripts/grind.mjs +++ b/scripts/grind.mjs @@ -66,36 +66,26 @@ if (!pkgRoot) { const fileRelToPkg = relative(pkgRoot, absFile); -function detectRunner(pkgRootDir) { +function detectVitest(pkgRootDir) { const pkg = JSON.parse(readFileSync(resolve(pkgRootDir, 'package.json'), 'utf8')); const deps = { ...pkg.devDependencies, ...pkg.dependencies }; - if (deps.vitest) return 'vitest'; - if (deps.jest) return 'jest'; + if (deps.vitest) return true; - const testScript = pkg.scripts?.test ?? ''; - if (/\bvitest\b/.test(testScript)) return 'vitest'; - if (/\bjest\b/.test(testScript)) return 'jest'; + if (/\bvitest\b/.test(pkg.scripts?.test ?? '')) return true; - if (existsSync(resolve(pkgRootDir, 'jest.config.js')) || - existsSync(resolve(pkgRootDir, 'jest.config.ts')) || - existsSync(resolve(pkgRootDir, 'jest.config.mjs'))) return 'jest'; - if (existsSync(resolve(pkgRootDir, 'vitest.config.ts')) || + return ( + existsSync(resolve(pkgRootDir, 'vitest.config.ts')) || existsSync(resolve(pkgRootDir, 'vitest.config.js')) || - existsSync(resolve(pkgRootDir, 'vitest.config.mjs'))) return 'vitest'; - - return null; + existsSync(resolve(pkgRootDir, 'vitest.config.mjs')) + ); } -const runner = detectRunner(pkgRoot); -if (!runner) { - console.error(`Could not detect vitest or jest in ${pkgRoot}/package.json`); +if (!detectVitest(pkgRoot)) { + console.error(`Could not detect vitest in ${pkgRoot}/package.json`); process.exit(2); } -const runnerArgs = - runner === 'vitest' - ? ['vitest', 'run', fileRelToPkg, '--reporter=dot'] - : ['jest', fileRelToPkg, '--colors=false']; +const runnerArgs = ['vitest', 'run', fileRelToPkg, '--reporter=dot']; let passed = 0; let firstFailureLogged = false; diff --git a/scripts/mutation-health/build-matrix.test.mjs b/scripts/mutation-health/build-matrix.test.mjs index c3d9489d33a..24fcd0915ed 100644 --- a/scripts/mutation-health/build-matrix.test.mjs +++ b/scripts/mutation-health/build-matrix.test.mjs @@ -65,10 +65,6 @@ describe('findPackageForSourceFile', () => { }); it('returns undefined for unowned paths', () => { - assert.equal( - findPackageForSourceFile('packages/@n8n/expression-runtime/src/foo.ts', ELIGIBLE), - undefined, - ); assert.equal(findPackageForSourceFile('README.md', ELIGIBLE), undefined); }); }); @@ -88,7 +84,7 @@ describe('buildMatrixRowForFile (on-demand re-score path)', () => { it('throws for files outside ELIGIBLE_PACKAGES', () => { assert.throws( - () => buildMatrixRowForFile('packages/cli/src/server.ts', ELIGIBLE), + () => buildMatrixRowForFile('packages/nodes-base/utils/binary.ts', ELIGIBLE), /No mutation-tracked package owns/, ); }); @@ -332,8 +328,8 @@ describe('buildMatrixFromPicked (global picker → matrix shape)', () => { // builder dies rather than spawning a mutate job we can't service. const picked = [ { - source_file_path: 'packages/@n8n/expression-runtime/src/foo.ts', - package: '@n8n/expression-runtime', + source_file_path: 'packages/nodes-base/utils/binary.ts', + package: 'n8n-nodes-base', prior_status: 'new', effective_status: 'new', value: 1, diff --git a/scripts/mutation-health/pick-next.mjs b/scripts/mutation-health/pick-next.mjs index af0689cdaed..6360bdcae94 100644 --- a/scripts/mutation-health/pick-next.mjs +++ b/scripts/mutation-health/pick-next.mjs @@ -118,14 +118,14 @@ const NON_SOURCE_DIRS = new Set(['__tests__', '__mocks__', 'fixtures']); // The vitest-eligible mutation-tracked packages. Single source of truth for // global mode: the picker walks every entry here unless overridden via -// --block. Adding a package = one-line append here AND in -// .github/workflows/mutation-health-nightly.yml `setup` job. Jest packages -// and the isolated-vm engine (@n8n/expression-runtime, blocked on DEVP-257) -// are intentionally absent. +// --block. The nightly workflow's `setup` job builds its matrix from this +// export (DEVP-497), so adding a package = one-line append here, nothing else. export const ELIGIBLE_PACKAGES = [ { name: 'n8n-workflow', dir: 'packages/workflow' }, { name: '@n8n/crdt', dir: 'packages/@n8n/crdt' }, { name: '@n8n/decorators', dir: 'packages/@n8n/decorators' }, + { name: '@n8n/expression-runtime', dir: 'packages/@n8n/expression-runtime' }, + { name: 'n8n', dir: 'packages/cli' }, ]; export function isEligible(pkgName) { diff --git a/scripts/mutation-health/pick-next.test.mjs b/scripts/mutation-health/pick-next.test.mjs index 60cfa88a68d..81a4f55ac6e 100644 --- a/scripts/mutation-health/pick-next.test.mjs +++ b/scripts/mutation-health/pick-next.test.mjs @@ -108,10 +108,7 @@ describe('isEligible (vitest allowlist unit test)', () => { }); it('rejects packages outside the allowlist', () => { - // expression-runtime is intentionally blocked on DEVP-257 - assert.equal(isEligible('@n8n/expression-runtime'), false); - // jest packages aren't onboarded yet - assert.equal(isEligible('@n8n/cli'), false); + assert.equal(isEligible('@n8n/foo'), false); assert.equal(isEligible(''), false); assert.equal(isEligible(undefined), false); });