Three stores, three importers and three pickers that differ only in their type are one of each (#85)
game-maker / gate (push) Successful in 1m33s
game-maker / suite (push) Successful in 5m46s

Refactoring round one: HttpFontStore/HttpSoundStore/HttpTextureStore collapse into a generic
HttpFileStore over a FileStore contract; fontImport/soundImport/textureImport share assetImport
with an ImportKind per sort; ImagePicker/FontPicker/SoundPicker sit on FilePicker and describe
only their row. The group/comment drag logic leaves GraphCanvas for annotationDrag.ts (with its
own unit test), declaredRows.ts replaces the duplicated row build in particles.ts and shaders.ts,
and dead code in font/sound/compile/emission/transform/actions is gone. README: asset list,
check count 958, the two-job CI, the Scenes wiki page.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Fable 5.1 committed 2026-09-16 17:38:54 +02:00
1 parent 15bdfdf07a
commit cc6db5f350
42 files changed
+1375 -1650

No files matched your search

+8 -8
View File
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import { HttpProjectStore } from '../../src/core/store/HttpProjectStore';
import { HttpTextureStore } from '../../src/core/store/HttpTextureStore';
import { HttpFileStore } from '../../src/core/store/HttpFileStore';
import { StaleRevisionError } from '../../src/core/store/ProjectStore';
import { makeProject } from '../helpers';
@@ -46,11 +46,11 @@ describe('HttpProjectStore', () => {
});
});
describe('HttpTextureStore', () => {
describe('HttpFileStore', () => {
it('uploads the raw bytes and returns what the server stored', async () => {
const mock = stubFetch(JSON.stringify({ file: FILE, width: 2, height: 3, bytes: 4 }), { status: 201 });
const bytes = new Blob([new Uint8Array([1, 2, 3, 4])]);
const stored = await new HttpTextureStore('/api').upload(bytes, 'image/png');
const stored = await new HttpFileStore('/api', 'textures').upload(bytes, 'image/png');
expect(stored).toEqual({ file: FILE, width: 2, height: 3, bytes: 4 });
const [url, init] = mock.mock.calls[0]!;
@@ -66,19 +66,19 @@ describe('HttpTextureStore', () => {
const bytes = new Blob([new Uint8Array(4)]);
let mock = stubFetch(answer, { status: 201 });
await new HttpTextureStore('/api').upload(bytes, 'image/png', 'hero idle.png');
await new HttpFileStore('/api', 'textures').upload(bytes, 'image/png', 'hero idle.png');
// The body stays the raw image, so the name travels in the query – encoded, not as a path.
expect(mock.mock.calls[0]![0]).toBe('/api?r=textures&name=hero%20idle.png');
mock = stubFetch(answer, { status: 201 });
await new HttpTextureStore('/api').upload(bytes, 'image/png');
await new HttpFileStore('/api', 'textures').upload(bytes, 'image/png');
expect(mock.mock.calls[0]![0]).toBe('/api?r=textures');
});
it('reports the nginx size limit as a size problem', async () => {
stubFetch(NGINX_413, { status: 413, statusText: 'Request Entity Too Large' });
const bytes = new Blob([new Uint8Array(8)]);
await expect(new HttpTextureStore('/api').upload(bytes, 'image/png')).rejects.toThrow(
await expect(new HttpFileStore('/api', 'textures').upload(bytes, 'image/png')).rejects.toThrow(
/too large \(413\)/i,
);
});
@@ -87,12 +87,12 @@ describe('HttpTextureStore', () => {
stubFetch(JSON.stringify([{ file: FILE, width: 8, height: 4, bytes: 12, updatedAt: 'now' }]), {
status: 200,
});
await expect(new HttpTextureStore('/api').list()).resolves.toEqual([
await expect(new HttpFileStore('/api', 'textures').list()).resolves.toEqual([
{ file: FILE, width: 8, height: 4, bytes: 12, updatedAt: 'now' },
]);
const mock = stubFetch(null, { status: 204 });
await new HttpTextureStore('/api').delete(FILE);
await new HttpFileStore('/api', 'textures').delete(FILE);
expect(mock.mock.calls[0]![0]).toBe(`/api?r=${encodeURIComponent(`textures/${FILE}`)}`);
expect(mock.mock.calls[0]![1]?.method).toBe('DELETE');
});