From 0b916de34503681b7784f2d4995b0690a88be8bc Mon Sep 17 00:00:00 2001 From: ASD Agent Date: Mon, 24 Aug 2026 23:30:07 -0700 Subject: [PATCH] ASD checkpoint implement: Implemented literal, case-sensitive grep matching with focused regression coverage; the final test suite passes and the complete diff contains only the tw --- config/commands.js | 3 +- tests/commands.test.js | 87 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/config/commands.js b/config/commands.js index 0ad60b7b..1b96379c 100644 --- a/config/commands.js +++ b/config/commands.js @@ -385,7 +385,8 @@ const commands = { if (_filesHere().includes(filename)) { let file = getFileContents(filename); - const matches = file.matchAll(q); + const literalQuery = q.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const matches = file.matchAll(literalQuery); for (const match of matches) { file = file.replaceAll(match[0], colorText(match[0], "files")); } diff --git a/tests/commands.test.js b/tests/commands.test.js index bd1cf2bf..701a2550 100644 --- a/tests/commands.test.js +++ b/tests/commands.test.js @@ -114,7 +114,13 @@ describe("apply", () => { // Loads the full command set with a fake terminal. `cd` is the one command with // real branching logic — a switch over ~, .., /home, /bin and team member names // — and it drives term.cwd, which the prompt renders on every keystroke. -function loadCommands({ cwd = "~", user = "guest", team = { avidan: {} } } = {}) { +function loadCommands({ + cwd = "~", + user = "guest", + team = { avidan: {} }, + files = {}, + colorText = (text) => text, +} = {}) { const term = { cwd, user, @@ -132,7 +138,11 @@ function loadCommands({ cwd = "~", user = "guest", team = { avidan: {} } } = {}) team, help: {}, portfolio: {}, - colorText: (text) => text, + colorText, + _DIRS: { [cwd]: Object.keys(files) }, + _FILES: Object.fromEntries(Object.keys(files).map((name) => [name, name])), + _filesHere: () => Object.keys(files), + getFileContents: (filename) => files[filename], window: {}, }); vm.runInContext(commandSource, context); @@ -145,6 +155,79 @@ function loadCommands({ cwd = "~", user = "guest", team = { avidan: {} } } = {}) return { commands, term }; } +describe("grep", () => { + it("matches and highlights a metacharacter literally", () => { + const { commands, term } = loadCommands({ + files: { "sample.txt": "before ( after (" }, + colorText: (text, style) => `<${style}>${text}`, + }); + + expect(() => commands.grep(["(", "sample.txt"])).not.toThrow(); + expect(term.writeln).toHaveBeenCalledWith( + "before ( after (" + ); + }); + + it("does not throw for regex metacharacters", () => { + const { commands } = loadCommands({ + files: { "sample.txt": "()[]$^*+?|\\" }, + }); + + for (const pattern of ["(", ")", "[", "]", "$", "^", "*", "+", "?", "|", "\\"]) { + expect(() => commands.grep([pattern, "sample.txt"])).not.toThrow(); + } + }); + + it("matches metacharacters literally and case-sensitively", () => { + const { commands, term } = loadCommands({ + files: { "sample.txt": "A+B a+b" }, + colorText: (text, style) => `<${style}>${text}`, + }); + + commands.grep(["+", "sample.txt"]); + + expect(term.writeln).toHaveBeenCalledWith( + "A+B a+b" + ); + }); + + it("highlights only literal dots", () => { + const { commands, term } = loadCommands({ + files: { "sample.txt": "one.two\n..." }, + colorText: (text, style) => `<${style}>${text}`, + }); + + commands.grep([".", "sample.txt"]); + + expect(term.writeln).toHaveBeenCalledWith( + "one.two\n..." + ); + }); + + it("preserves plain-pattern highlighting and missing-argument usage", () => { + const matching = loadCommands({ + files: { "sample.txt": "foo bar foo" }, + colorText: (text, style) => `<${style}>${text}`, + }); + matching.commands.grep(["foo", "sample.txt"]); + expect(matching.term.writeln).toHaveBeenCalledWith( + "foo bar foo" + ); + + const missing = loadCommands(); + missing.commands.grep([]); + expect(missing.term.stylePrint).toHaveBeenCalledWith( + "usage: %grep% [pattern] [filename]" + ); + + const notFound = loadCommands(); + notFound.commands.grep(["foo", "missing.txt"]); + expect(notFound.term.stylePrint).toHaveBeenCalledWith( + "No such file or directory: missing.txt" + ); + }); +}); + describe("cd", () => { // Table ported from #51 (@astonm, 2021), which never landed. The cases still // describe the intended behaviour; only the harness has changed.