Refine Media Manager Save Logic and UI

- Add failing global function verification test (GlobalFunctions.test.ts) and fix missing exports in global.ts.
- Refactor MediaManager.html UI:
    - Implement 
enderPlanHtml to standardize Plan (Details) and Execution views.
    - Show 'Skipped' state for empty save phases.
    - Visually decouple 'Sheet Update' from 'Reorder' phase.
    - Separate 'Manual Link' operations into their own 'Linking' section in the plan view, distinct from Adoptions.
    - Fix TypeErrors in 
enderPlanHtml (undefined actions) and 
enderMatch (missing DOM elements).
- Update MediaService.test.ts to match new filename constraints on reorder.
- Update mediaHandlers.test.ts to correctly spy on loose MediaService instances.
- Ensure all tests pass.
This commit is contained in:
Ben Miller
2026-01-01 08:04:06 -07:00
parent 8d780d2fcb
commit 2c01693271
10 changed files with 863 additions and 307 deletions

View File

@ -0,0 +1,81 @@
import * as fs from 'fs';
import * as path from 'path';
describe('Global Function Exports', () => {
const srcDir = path.resolve(__dirname, '../'); // Assumes src/test/GlobalFunctions.test.ts
const globalFile = path.join(srcDir, 'global.ts');
// 1. Get all globally exported function names
const getGlobalExports = (): Set<string> => {
const content = fs.readFileSync(globalFile, 'utf-8');
const regex = /;\(global as any\)\.(\w+)\s*=/g;
const exports = new Set<string>();
let match;
while ((match = regex.exec(content)) !== null) {
exports.add(match[1]);
}
return exports;
};
// 2. Find all google.script.run calls in HTML files
const getFrontendCalls = (): Map<string, string> => {
const calls = new Map<string, string>(); // functionName -> filename (for error msg)
const scanDir = (dir: string) => {
const files = fs.readdirSync(dir);
for (const file of files) {
const fullPath = path.join(dir, file);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
scanDir(fullPath);
} else if (file.endsWith('.html')) {
const content = fs.readFileSync(fullPath, 'utf-8');
// Matches:
// google.script.run.myFunc()
// google.script.run.withSuccessHandler(...).myFunc()
// google.script.run.withFailureHandler(...).myFunc()
// google.script.run.withSuccessHandler(...).withFailureHandler(...).myFunc()
// Regex strategy:
// 1. Find "google.script.run"
// 2. Consume optional handlers .with...(...)
// 3. Capture the final function name .FunctionName(
const callRegex = /google\.script\.run(?:[\s\n]*\.(?:withSuccessHandler|withFailureHandler|withUserObject)\([^)]*\))*[\s\n]*\.(\w+)\s*\(/g;
let match;
while ((match = callRegex.exec(content)) !== null) {
const funcName = match[1];
if (!['withSuccessHandler', 'withFailureHandler', 'withUserObject'].includes(funcName)) {
calls.set(funcName, file);
}
}
}
}
};
scanDir(srcDir);
return calls;
};
test('All client-side google.script.run calls must be exported in global.ts', () => {
const globalExports = getGlobalExports();
const frontendCalls = getFrontendCalls();
const missingQuery = [];
frontendCalls.forEach((filename, funcName) => {
if (!globalExports.has(funcName)) {
missingQuery.push(`${funcName} (called in ${filename})`);
}
});
if (missingQuery.length > 0) {
throw new Error(
`The following backend functions are called from the frontend but missing from src/global.ts:\n` +
missingQuery.join('\n') +
`\n\nPlease add them to src/global.ts like: ;(global as any).${missingQuery[0].split(' ')[0]} = ...`
);
}
});
});