Skip to content

Commit

Permalink
Improve Stubscape
Browse files Browse the repository at this point in the history
Update the Stubscape implementation so that the behavior of `quote` and
`quoteAll` is more similar to the real implementation of Shescape. Tests
are updated accordingly, mostly resulting in cleaner integration tests
(with more coverage) at the minor cost of more specific unit tests (that
actually more accurately reflect expected behavior).

Also update the Stubscape JSDoc to be up-to-date, both with respect to
the changes seen here as well as the changes of v3.0.0.
  • Loading branch information
ericcornelissen committed Oct 10, 2023
1 parent b754158 commit bb51695
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 21 deletions.
34 changes: 30 additions & 4 deletions test/integration/testing/commonjs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,24 @@ testProp(
"Stubscape#quote (esm === cjs)",
[arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
(t, arg, options) => {
let resultEsm, resultCjs, erroredEsm, erroredCjs;

const stubscape = new Stubscape(options);
const stubscapeCjs = new StubscapeCjs(options);

const resultEsm = stubscape.quote(arg);
const resultCjs = stubscapeCjs.quote(arg);
try {
resultEsm = stubscape.quote(arg);
} catch (_) {
erroredEsm = true;
}

try {
resultCjs = stubscapeCjs.quote(arg);
} catch (_) {
erroredCjs = true;
}

t.is(erroredEsm, erroredCjs);
t.is(resultEsm, resultCjs);
},
);
Expand All @@ -58,11 +71,24 @@ testProp(
"Stubscape#quoteAll (esm === cjs)",
[fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
(t, args, options) => {
let resultEsm, resultCjs, erroredEsm, erroredCjs;

const stubscape = new Stubscape(options);
const stubscapeCjs = new StubscapeCjs(options);

const resultEsm = stubscape.quoteAll(args);
const resultCjs = stubscapeCjs.quoteAll(args);
try {
resultEsm = stubscape.quoteAll(args);
} catch (_) {
erroredEsm = true;
}

try {
resultCjs = stubscapeCjs.quoteAll(args);
} catch (_) {
erroredCjs = true;
}

t.is(erroredEsm, erroredCjs);
t.deepEqual(resultEsm, resultCjs);
},
);
Expand Down
10 changes: 2 additions & 8 deletions test/integration/testing/functional.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,7 @@ testProp(

testProp(
"Stubscape#quote, with shell (stubscape =~ shescape)",
[
fc.anything(),
arbitrary.shescapeOptions().filter((options) => options?.shell !== false),
],
[fc.anything(), arbitrary.shescapeOptions()],
(t, arg, options) => {
let result, stubResult, errored, stubErrored;

Expand Down Expand Up @@ -113,10 +110,7 @@ testProp(

testProp(
"Stubscape#quoteAll, with shell (stubscape =~ shescape)",
[
fc.anything(),
arbitrary.shescapeOptions().filter((options) => options?.shell !== false),
],
[fc.anything(), arbitrary.shescapeOptions()],
(t, args, options) => {
let result, stubResult, errored, stubErrored;

Expand Down
44 changes: 40 additions & 4 deletions test/unit/testing/stubscape.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ test("escapeAll invalid arguments", (t) => {

testProp(
"quote valid arguments",
[arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
[
arbitrary.shescapeArg(),
arbitrary.shescapeOptions().filter((options) => options?.shell !== false),
],
(t, arg, options) => {
const stubscape = new Stubscape(options);
const result = stubscape.quote(arg);
Expand All @@ -87,9 +90,26 @@ test("quote invalid arguments", (t) => {
}
});

testProp(
"quote without a shell",
[
arbitrary.shescapeArg(),
arbitrary.shescapeOptions().filter((options) => options?.shell === false),
],
(t, arg, options) => {
const stubscape = new Stubscape(options);
t.throws(() => stubscape.quote(arg), {
instanceOf: Error,
});
},
);

testProp(
"quoteAll valid arguments",
[fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
[
fc.array(arbitrary.shescapeArg()),
arbitrary.shescapeOptions().filter((options) => options?.shell !== false),
],
(t, args, options) => {
const stubscape = new Stubscape(options);
const result = stubscape.quoteAll(args);
Expand All @@ -100,12 +120,14 @@ testProp(

testProp(
"quoteAll non-array arguments",
[arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
[
arbitrary.shescapeArg(),
arbitrary.shescapeOptions().filter((options) => options?.shell !== false),
],
(t, arg, options) => {
const stubscape = new Stubscape(options);
t.throws(() => stubscape.quoteAll(arg), {
instanceOf: TypeError,
message: "args.map is not a function",
});
},
);
Expand All @@ -120,3 +142,17 @@ test("quoteAll invalid arguments", (t) => {
});
}
});

testProp(
"quoteAll without a shell",
[
fc.array(arbitrary.shescapeArg()),
arbitrary.shescapeOptions().filter((options) => options?.shell === false),
],
(t, args, options) => {
const stubscape = new Stubscape(options);
t.throws(() => stubscape.quoteAll(args), {
instanceOf: Error,
});
},
);
20 changes: 15 additions & 5 deletions testing.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,19 @@ export const injectionStrings = [
];

/**
* A test stub of Shescape that has the same input-output profile as the real
* Shescape implementation.
* An optimistic test stub of Shescape that has the same input-output profile as
* the real Shescape implementation.
*
* In particular:
* - The constructor never fails.
* - Returns a string for all stringable inputs.
* - Errors on non-stringable inputs.
* - Converts non-array inputs to single-item arrays where necessary.
* - Errors on non-array inputs where arrays are expected.
* - Errors when trying to quote when `shell: false`.
*/
export class Shescape {
constructor(_options) {
// Nothing to do.
constructor(options = {}) {
this.shell = options.shell;
}

escape(arg) {
Expand All @@ -48,10 +50,18 @@ export class Shescape {
}

quote(arg) {
if (this.shell === false) {
throw new Error();
}

return this.escape(arg);
}

quoteAll(args) {
if (this.shell === false) {
throw new Error();
}

return this.escapeAll(args);
}
}
Expand Down

0 comments on commit bb51695

Please sign in to comment.