From 26f3c88902068001342aa27d06a1c243aad03b16 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Fri, 13 Feb 2026 18:23:55 -0800 Subject: [PATCH] test: add Round 120 tests for replaceInFile empty search, setAlias length boundary, and notes extraction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - replaceInFile: empty string search — replace prepends at pos 0, replaceAll inserts between every char - setAlias: 128-char alias accepted (boundary), 129-char rejected (> 128 check) - parseSessionMetadata: "Notes for Next Session" extraction — last section, empty, ### boundary, markdown Total tests: 920 --- tests/lib/session-aliases.test.js | 31 +++++++++++++++++++++++++ tests/lib/session-manager.test.js | 38 +++++++++++++++++++++++++++++++ tests/lib/utils.test.js | 38 +++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) diff --git a/tests/lib/session-aliases.test.js b/tests/lib/session-aliases.test.js index c1c7c283..bc4de4b4 100644 --- a/tests/lib/session-aliases.test.js +++ b/tests/lib/session-aliases.test.js @@ -1647,6 +1647,37 @@ function runTests() { assert.strictEqual(validResult.success, true, 'Non-reserved name should succeed'); })) passed++; else failed++; + // ── Round 120: setAlias max length boundary — 128 accepted, 129 rejected ── + console.log('\nRound 120: setAlias (max alias length boundary — 128 ok, 129 rejected):'); + if (test('setAlias accepts exactly 128-char alias name but rejects 129 chars (> 128 boundary)', () => { + resetAliases(); + + // 128 characters — exactly at limit (alias.length > 128 is false) + const name128 = 'a'.repeat(128); + const result128 = aliases.setAlias(name128, '/path/to/session'); + assert.strictEqual(result128.success, true, + '128-char alias should be accepted (128 > 128 is false)'); + + // 129 characters — just over limit + const name129 = 'a'.repeat(129); + const result129 = aliases.setAlias(name129, '/path/to/session'); + assert.strictEqual(result129.success, false, + '129-char alias should be rejected (129 > 128 is true)'); + assert.ok(result129.error.includes('128'), + 'Error should mention the 128 character limit'); + + // 1 character — minimum valid + const name1 = 'x'; + const result1 = aliases.setAlias(name1, '/path/to/session'); + assert.strictEqual(result1.success, true, + 'Single character alias should be accepted'); + + // Verify the 128-char alias was actually stored + const resolved = aliases.resolveAlias(name128); + assert.ok(resolved, '128-char alias should be resolvable'); + assert.strictEqual(resolved.sessionPath, '/path/to/session'); + })) passed++; else failed++; + // Summary console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`); process.exit(failed > 0 ? 1 : 0); diff --git a/tests/lib/session-manager.test.js b/tests/lib/session-manager.test.js index 4d1617d7..551a794c 100644 --- a/tests/lib/session-manager.test.js +++ b/tests/lib/session-manager.test.js @@ -2246,6 +2246,44 @@ file.ts 'Empty code block should result in empty context (trim of empty)'); })) passed++; else failed++; + // ── Round 120: parseSessionMetadata "Notes for Next Session" extraction edge cases ── + console.log('\nRound 120: parseSessionMetadata ("Notes for Next Session" — extraction edge cases):'); + if (test('parseSessionMetadata extracts notes section — last section, empty, followed by ###', () => { + // Notes as the last section (no ### or \n\n after) + const lastSection = '# Session\n\n### Notes for Next Session\nRemember to review PR #42\nAlso check CI status'; + const lastMeta = sessionManager.parseSessionMetadata(lastSection); + assert.strictEqual(lastMeta.notes, 'Remember to review PR #42\nAlso check CI status', + 'Notes as last section should capture everything to end of string via $ anchor'); + assert.strictEqual(lastMeta.hasNotes, undefined, + 'hasNotes is not a direct property of parseSessionMetadata result'); + + // Notes followed by another ### section + const withNext = '# Session\n\n### Notes for Next Session\nImportant note\n### Context to Load\n```\nfiles\n```'; + const nextMeta = sessionManager.parseSessionMetadata(withNext); + assert.strictEqual(nextMeta.notes, 'Important note', + 'Notes should stop at next ### header'); + + // Notes followed by \n\n (double newline) + const withDoubleNewline = '# Session\n\n### Notes for Next Session\nNote here\n\nSome other text'; + const dblMeta = sessionManager.parseSessionMetadata(withDoubleNewline); + assert.strictEqual(dblMeta.notes, 'Note here', + 'Notes should stop at \\n\\n boundary'); + + // Empty notes section (header only, followed by \n\n) + const emptyNotes = '# Session\n\n### Notes for Next Session\n\n### Other Section'; + const emptyMeta = sessionManager.parseSessionMetadata(emptyNotes); + assert.strictEqual(emptyMeta.notes, '', + 'Empty notes section should result in empty string after trim'); + + // Notes with markdown formatting + const markdownNotes = '# Session\n\n### Notes for Next Session\n- [ ] Review **important** PR\n- [x] Check `config.js`\n\n### Done'; + const mdMeta = sessionManager.parseSessionMetadata(markdownNotes); + assert.ok(mdMeta.notes.includes('**important**'), + 'Markdown bold should be preserved in notes'); + assert.ok(mdMeta.notes.includes('`config.js`'), + 'Markdown code should be preserved in notes'); + })) passed++; else failed++; + // Summary console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`); process.exit(failed > 0 ? 1 : 0); diff --git a/tests/lib/utils.test.js b/tests/lib/utils.test.js index ecb9e35b..cdeb6104 100644 --- a/tests/lib/utils.test.js +++ b/tests/lib/utils.test.js @@ -2017,6 +2017,44 @@ function runTests() { } })) passed++; else failed++; + // ── Round 120: replaceInFile with empty string search — prepend vs insert-between-every-char ── + console.log('\nRound 120: replaceInFile (empty string search — replace vs replaceAll dramatic difference):'); + if (test('replaceInFile with empty search: replace prepends at pos 0; replaceAll inserts between every char', () => { + const tmpDir = fs.mkdtempSync(path.join(utils.getTempDir(), 'r120-empty-search-')); + const testFile = path.join(tmpDir, 'test.txt'); + try { + // Without options.all: .replace('', 'X') prepends at position 0 + fs.writeFileSync(testFile, 'hello'); + utils.replaceInFile(testFile, '', 'X'); + const prepended = utils.readFile(testFile); + assert.strictEqual(prepended, 'Xhello', + 'replace("", "X") should prepend X at position 0 only'); + + // With options.all: .replaceAll('', 'X') inserts between every character + fs.writeFileSync(testFile, 'hello'); + utils.replaceInFile(testFile, '', 'X', { all: true }); + const insertedAll = utils.readFile(testFile); + assert.strictEqual(insertedAll, 'XhXeXlXlXoX', + 'replaceAll("", "X") inserts X at every position boundary'); + + // Empty file + empty search + fs.writeFileSync(testFile, ''); + utils.replaceInFile(testFile, '', 'X'); + const emptyReplace = utils.readFile(testFile); + assert.strictEqual(emptyReplace, 'X', + 'Empty content + empty search: single insertion at position 0'); + + // Empty file + empty search + all + fs.writeFileSync(testFile, ''); + utils.replaceInFile(testFile, '', 'X', { all: true }); + const emptyAll = utils.readFile(testFile); + assert.strictEqual(emptyAll, 'X', + 'Empty content + replaceAll("", "X"): single position boundary → "X"'); + } finally { + fs.rmSync(tmpDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + // Summary console.log('\n=== Test Results ==='); console.log(`Passed: ${passed}`);