Single source of truth: combat logic, storage parity, slot ordering #3
+1
-1
@@ -27,7 +27,7 @@
|
|||||||
"server:dev": "npm run dev --workspace server",
|
"server:dev": "npm run dev --workspace server",
|
||||||
"server:test": "npm test --workspace server",
|
"server:test": "npm test --workspace server",
|
||||||
"shared:test": "npm test --workspace shared",
|
"shared:test": "npm test --workspace shared",
|
||||||
"test:all": "npm run shared:test && npm run server:test"
|
"test:all": "CI=true react-scripts test --watchAll=false && npm run shared:test && npm run server:test"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Combat.scenario.test.js
|
// Combat.scenario.test.js
|
||||||
// Full combat scenario driven through shared/turn.js directly — NO React.
|
// Full combat scenario driven through shared/turn.js directly --- NO React.
|
||||||
//
|
//
|
||||||
// Does 100 ROUNDS (not turns). A round = one full pass through initiative
|
// Does 100 ROUNDS (not turns). A round = one full pass through initiative
|
||||||
// (every active participant acts once); round counter increments at wrap.
|
// (every active participant acts once); round counter increments at wrap.
|
||||||
@@ -311,10 +311,6 @@ test('full 100-round combat scenario (shared, no React)', async () => {
|
|||||||
|
|
||||||
await recordAsync('endCombat', () => endCombat());
|
await recordAsync('endCombat', () => endCombat());
|
||||||
|
|
||||||
// sanity log: prove we actually ran ROUNDS rounds, not ~ROUNDS turns
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(`\nscenario: ${roundsDone} rounds, ${turnTotal} turns\n`);
|
|
||||||
|
|
||||||
// ---------- report ----------
|
// ---------- report ----------
|
||||||
const failed = RESULTS.filter(x => !x.ok);
|
const failed = RESULTS.filter(x => !x.ok);
|
||||||
if (failed.length > 0) {
|
if (failed.length > 0) {
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// Firebase adapter contract test.
|
||||||
|
// Runs the SAME storage contract as memory + ws (src/storage/contract.js)
|
||||||
|
// against createFirebaseStorage. The SDK is mocked via src/__mocks__/firebase/*,
|
||||||
|
// so this proves the adapter translates contract calls -> SDK calls correctly
|
||||||
|
// (path handling, merge semantics, subscribe) without hitting network.
|
||||||
|
|
||||||
|
import { initFirebase, createFirebaseStorage } from '../storage/firebase';
|
||||||
|
import { runStorageContract } from '../storage/contract';
|
||||||
|
import { resetMockDb } from '../__mocks__/firebase/_mock-db';
|
||||||
|
|
||||||
|
// Firebase mock DB is shared global state. Reset before each factory so
|
||||||
|
// contract tests are isolated.
|
||||||
|
runStorageContract('firebase', () => {
|
||||||
|
resetMockDb();
|
||||||
|
const ok = initFirebase();
|
||||||
|
if (!ok) throw new Error('initFirebase failed under mocked env');
|
||||||
|
return createFirebaseStorage();
|
||||||
|
});
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// Storage factory (index.js) test.
|
||||||
|
// Verifies getStorage() returns the right adapter per REACT_APP_STORAGE env,
|
||||||
|
// caches as singleton, and getStorageMode() reports the active mode.
|
||||||
|
// Adapters are mocked (no network/init) — factory routing is the unit.
|
||||||
|
|
||||||
|
import { getStorage, getStorageMode } from '../storage/index';
|
||||||
|
|
||||||
|
// reset module-level singleton between tests
|
||||||
|
let originalStorage;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
originalStorage = process.env.REACT_APP_STORAGE;
|
||||||
|
// bust the module singleton by re-importing fresh
|
||||||
|
jest.resetModules();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (originalStorage === undefined) delete process.env.REACT_APP_STORAGE;
|
||||||
|
else process.env.REACT_APP_STORAGE = originalStorage;
|
||||||
|
jest.resetModules();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getStorageMode', () => {
|
||||||
|
test('defaults to firebase when env unset', () => {
|
||||||
|
delete process.env.REACT_APP_STORAGE;
|
||||||
|
const { getStorageMode } = require('../storage/index');
|
||||||
|
expect(getStorageMode()).toBe('firebase');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns ws when REACT_APP_STORAGE=ws', () => {
|
||||||
|
process.env.REACT_APP_STORAGE = 'ws';
|
||||||
|
const { getStorageMode } = require('../storage/index');
|
||||||
|
expect(getStorageMode()).toBe('ws');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns memory when REACT_APP_STORAGE=memory', () => {
|
||||||
|
process.env.REACT_APP_STORAGE = 'memory';
|
||||||
|
const { getStorageMode } = require('../storage/index');
|
||||||
|
expect(getStorageMode()).toBe('memory');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getStorage factory routing', () => {
|
||||||
|
test('memory mode returns memory adapter', () => {
|
||||||
|
process.env.REACT_APP_STORAGE = 'memory';
|
||||||
|
const { getStorage } = require('../storage/index');
|
||||||
|
const s = getStorage();
|
||||||
|
expect(s).toBeTruthy();
|
||||||
|
expect(typeof s.getDoc).toBe('function');
|
||||||
|
expect(typeof s.setDoc).toBe('function');
|
||||||
|
expect(typeof s.subscribeDoc).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns singleton on repeat call (same instance)', () => {
|
||||||
|
process.env.REACT_APP_STORAGE = 'memory';
|
||||||
|
const { getStorage } = require('../storage/index');
|
||||||
|
const a = getStorage();
|
||||||
|
const b = getStorage();
|
||||||
|
expect(a).toBe(b);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ws mode returns ws adapter (init may fail without backend — catch)', () => {
|
||||||
|
process.env.REACT_APP_STORAGE = 'ws';
|
||||||
|
const { getStorage } = require('../storage/index');
|
||||||
|
try {
|
||||||
|
const s = getStorage();
|
||||||
|
expect(s).toBeTruthy();
|
||||||
|
expect(typeof s.getDoc).toBe('function');
|
||||||
|
} catch (e) {
|
||||||
|
// ws adapter without backend URL/config is allowed to throw at factory;
|
||||||
|
// routing reached ws branch (not memory/firebase) which is the contract.
|
||||||
|
expect(e).toBeTruthy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user