From 242fe3e2963b827c2e559f2e81ee6e535de11ee6 Mon Sep 17 00:00:00 2001 From: Gus Date: Mon, 11 Aug 2025 19:03:26 +0800 Subject: [PATCH 1/8] test: stub API URL for client tests --- tests/stores/api/client.test.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/stores/api/client.test.ts b/tests/stores/api/client.test.ts index 6006c99c..c148153c 100644 --- a/tests/stores/api/client.test.ts +++ b/tests/stores/api/client.test.ts @@ -10,19 +10,18 @@ const options: ApiClientOptions = { const url = 'http://example.com/'; -// Ensure environment variable for base url -process.env.VITE_API_URL = url; - let client: ApiClient; beforeEach(() => { - client = new ApiClient(options); - localStorage.clear(); - vi.stubGlobal('fetch', vi.fn()); + vi.stubEnv('VITE_API_URL', url); + client = new ApiClient(options); + localStorage.clear(); + vi.stubGlobal('fetch', vi.fn()); }); afterEach(() => { - vi.restoreAllMocks(); + vi.restoreAllMocks(); + vi.unstubAllEnvs(); }); describe('ApiClient', () => { From 959889815de52910ebb1ebbac16fadef63608e76 Mon Sep 17 00:00:00 2001 From: Gus Date: Tue, 12 Aug 2025 09:47:38 +0800 Subject: [PATCH 2/8] Refactor ApiClient to use crypto.randomUUID and update tests for nonce --- tests/stores/api/client.test.ts | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/tests/stores/api/client.test.ts b/tests/stores/api/client.test.ts index c148153c..35169ab6 100644 --- a/tests/stores/api/client.test.ts +++ b/tests/stores/api/client.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; + import { ApiClient, ApiClientOptions } from '@api/client.ts'; import { HttpError } from '@api/http-error.ts'; @@ -11,10 +12,12 @@ const options: ApiClientOptions = { const url = 'http://example.com/'; let client: ApiClient; +let nonce: string; beforeEach(() => { vi.stubEnv('VITE_API_URL', url); client = new ApiClient(options); + nonce = 'nonce'; localStorage.clear(); vi.stubGlobal('fetch', vi.fn()); }); @@ -38,13 +41,13 @@ describe('ApiClient', () => { const data = { ok: true }; (fetch as vi.Mock).mockResolvedValue(new Response(JSON.stringify(data), { status: 200 })); - const result = await client.post('test', { id: 1 }); - expect(result).toEqual(data); + const result = await client.post('test', nonce, { id: 1 }); + expect(result).toEqual(data); - (fetch as vi.Mock).mockResolvedValue(new Response('fail', { status: 500, statusText: 'err' })); + (fetch as vi.Mock).mockResolvedValue(new Response('fail', { status: 500, statusText: 'err' })); - await expect(client.post('test', { id: 2 })).rejects.toBeInstanceOf(HttpError); - }); + await expect(client.post('test', nonce, { id: 2 })).rejects.toBeInstanceOf(HttpError); + }); it('caches get requests and serves from cache', async () => { // prefill cache @@ -53,9 +56,9 @@ describe('ApiClient', () => { (fetch as vi.Mock).mockResolvedValue(new Response(null, { status: 304 })); - const result = await client.get('test'); - expect(result).toEqual({ cached: true }); - }); + const result = await client.get('test', nonce); + expect(result).toEqual({ cached: true }); + }); it('stores response with etag in cache', async () => { const data = { foo: 'bar' }; @@ -66,14 +69,14 @@ describe('ApiClient', () => { }), ); - const result = await client.get('test'); - expect(result).toEqual(data); - expect(localStorage.getItem('api-cache-test')).not.toBeNull(); - }); + const result = await client.get('test', nonce); + expect(result).toEqual(data); + expect(localStorage.getItem('api-cache-test')).not.toBeNull(); + }); it('throws HttpError on failed get', async () => { (fetch as vi.Mock).mockResolvedValue(new Response('nope', { status: 404, statusText: 'NF' })); - await expect(client.get('oops')).rejects.toBeInstanceOf(HttpError); - }); + await expect(client.get('oops', nonce)).rejects.toBeInstanceOf(HttpError); + }); }); From 706d39b54363407a3bed647ee8ce67970fd783e3 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Tue, 12 Aug 2025 11:43:30 +0800 Subject: [PATCH 3/8] format --- tests/stores/api/client.test.ts | 42 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/stores/api/client.test.ts b/tests/stores/api/client.test.ts index 35169ab6..2e3f2a28 100644 --- a/tests/stores/api/client.test.ts +++ b/tests/stores/api/client.test.ts @@ -15,16 +15,16 @@ let client: ApiClient; let nonce: string; beforeEach(() => { - vi.stubEnv('VITE_API_URL', url); - client = new ApiClient(options); - nonce = 'nonce'; - localStorage.clear(); - vi.stubGlobal('fetch', vi.fn()); + vi.stubEnv('VITE_API_URL', url); + client = new ApiClient(options); + nonce = 'nonce'; + localStorage.clear(); + vi.stubGlobal('fetch', vi.fn()); }); afterEach(() => { - vi.restoreAllMocks(); - vi.unstubAllEnvs(); + vi.restoreAllMocks(); + vi.unstubAllEnvs(); }); describe('ApiClient', () => { @@ -41,13 +41,13 @@ describe('ApiClient', () => { const data = { ok: true }; (fetch as vi.Mock).mockResolvedValue(new Response(JSON.stringify(data), { status: 200 })); - const result = await client.post('test', nonce, { id: 1 }); - expect(result).toEqual(data); + const result = await client.post('test', nonce, { id: 1 }); + expect(result).toEqual(data); - (fetch as vi.Mock).mockResolvedValue(new Response('fail', { status: 500, statusText: 'err' })); + (fetch as vi.Mock).mockResolvedValue(new Response('fail', { status: 500, statusText: 'err' })); - await expect(client.post('test', nonce, { id: 2 })).rejects.toBeInstanceOf(HttpError); - }); + await expect(client.post('test', nonce, { id: 2 })).rejects.toBeInstanceOf(HttpError); + }); it('caches get requests and serves from cache', async () => { // prefill cache @@ -56,9 +56,9 @@ describe('ApiClient', () => { (fetch as vi.Mock).mockResolvedValue(new Response(null, { status: 304 })); - const result = await client.get('test', nonce); - expect(result).toEqual({ cached: true }); - }); + const result = await client.get('test', nonce); + expect(result).toEqual({ cached: true }); + }); it('stores response with etag in cache', async () => { const data = { foo: 'bar' }; @@ -69,14 +69,14 @@ describe('ApiClient', () => { }), ); - const result = await client.get('test', nonce); - expect(result).toEqual(data); - expect(localStorage.getItem('api-cache-test')).not.toBeNull(); - }); + const result = await client.get('test', nonce); + expect(result).toEqual(data); + expect(localStorage.getItem('api-cache-test')).not.toBeNull(); + }); it('throws HttpError on failed get', async () => { (fetch as vi.Mock).mockResolvedValue(new Response('nope', { status: 404, statusText: 'NF' })); - await expect(client.get('oops', nonce)).rejects.toBeInstanceOf(HttpError); - }); + await expect(client.get('oops', nonce)).rejects.toBeInstanceOf(HttpError); + }); }); From b8c852544cdb3bd121f161179639ab74bd8628aa Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Tue, 12 Aug 2025 11:51:33 +0800 Subject: [PATCH 4/8] node --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d2fba680..22f14ae1 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "type": "module", "engines": { - "node": ">=20" + "node": "22.18.0" }, "scripts": { "dev": "vite", From aa5ad20e5d8a3202feb69b70592fed5655c85c74 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Tue, 12 Aug 2025 11:57:15 +0800 Subject: [PATCH 5/8] engine-strict --- .npmrc | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..e29745af --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +engine-strict=true + From 59c4c112a014a988acc1eb22ec81ce1019b31891 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Tue, 12 Aug 2025 12:03:14 +0800 Subject: [PATCH 6/8] pre-install --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 22f14ae1..73587f5a 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "build": "vite build", "preview": "vite preview", "test": "vitest run --coverage", - "build:stats": "VITE_VISUALIZER=true vite build" + "build:stats": "VITE_VISUALIZER=true vite build", + "preinstall": "node -e \"const [maj,min]=process.versions.node.split('.').map(Number);if(maj!==22||min<12){console.error('Node 22.12+ required. Detected '+process.versions.node);process.exit(1)}\"" }, "dependencies": { "dompurify": "^3.2.6", From 8611c4938306e04daee34b8201bbf7478081831d Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Tue, 12 Aug 2025 12:08:33 +0800 Subject: [PATCH 7/8] add mise --- mise.toml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 mise.toml diff --git a/mise.toml b/mise.toml new file mode 100644 index 00000000..f208e7d8 --- /dev/null +++ b/mise.toml @@ -0,0 +1,3 @@ +[tools] +node = "22.18.0" + From 136c17bb9ec54174f00b5c44cb9d04b7ef6917e7 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Tue, 12 Aug 2025 12:13:18 +0800 Subject: [PATCH 8/8] wip --- .nvmrc | 1 + .tool-versions | 1 + 2 files changed, 2 insertions(+) create mode 100644 .nvmrc create mode 100644 .tool-versions diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..91d5f6ff --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22.18.0 diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..9df07365 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 22.18.0