Jest's toEqual matcher takes whitespace into account when checking for equality. When formatting the expected value in tests it is impossible to do so in a way that matches a string containing newlines, tabs etc.
Does Jest offer a way to disregard whitespace when matching?
Note: I edited the question to make it more generic.
As #Timo says, the only way of doing this appears to be with a custom matcher. Here is one that compresses all whitespace down to a single space for readability based on Jest's toEqual matcher. It will deal with tabs, newlines etc. It will give you pretty output like the included Jest matchers:
//matchStringIgnoringWhiteSpace.js
import { replace, map, equals } from 'ramda';
import { matcherHint, printReceived, printExpected } from 'jest-matcher-utils';
import diff from 'jest-diff';
const replaceWhitespace = replace(/\s+/g, ` `);
const compressWhitespace = map(replaceWhitespace);
const name = `toEqualWithCompressedWhitespace`;
export default function (received, expected) {
const [
receivedWithCompresssedWhitespace,
expectedWithCompresssedWhitespace,
] = compressWhitespace([received, expected]);
const pass = equals(
receivedWithCompresssedWhitespace,
expectedWithCompresssedWhitespace
);
const message = pass
? () =>
`${matcherHint(`.not.${name}`)}\n\n` +
`Uncompressed expected value:\n` +
` ${printExpected(expected)}\n` +
`Expected value with compressed whitespace to not equal:\n` +
` ${printExpected(expectedWithCompresssedWhitespace)}\n` +
`Uncompressed received value:\n` +
` ${printReceived(received)}\n` +
`Received value with compressed whitespace:\n` +
` ${printReceived(receivedWithCompresssedWhitespace)}`
: () => {
const diffString = diff(
expectedWithCompresssedWhitespace,
receivedWithCompresssedWhitespace,
{
expand: this.expand,
}
);
return (
`${matcherHint(`.${name}`)}\n\n` +
`Uncompressed expected value:\n` +
` ${printExpected(expected)}\n` +
`Expected value with compressed whitespace to equal:\n` +
` ${printExpected(expectedWithCompresssedWhitespace)}\n` +
`Uncompressed received value:\n` +
` ${printReceived(received)}\n` +
`Received value with compressed whitespace:\n` +
` ${printReceived(receivedWithCompresssedWhitespace)}${
diffString ? `\n\nDifference:\n\n${diffString}` : ``
}`
);
};
return {
actual: received,
expected,
message,
name,
pass,
};
};
To register the custom matcher you need to add it to your setupTests files. First register setupTests in your jest.config.js using the setupFilesAfterEnv field:
setupFilesAfterEnv: `<rootDir>/path/to/setupTests.js`,
And then register the custom matcher on the expect object.
//setupTests.js
import toMatchStringIgnoringWhitespace from "<rootDir>/path/to/matchStringIgnoringWhiteSpace";
expect.extend({
toMatchStringIgnoringWhitespace: toMatchStringIgnoringWhitespace
});
If you are using TypeScript you will also want to add the typings to the expect object following the instructions here.
As far as I know, there is no way to achieve this with Jest out of the box.
However, it is pretty straightforward to write your own reusable matcher using expect.extend. Remove all whitespace from both strings, e.g. via str.replace(/\s/g, ''), and compare the strings.
While this is not a direct answer, you can also do:
mockedFunction.mock.calls[0] // To get array of arguments
// or
mockedFunction.mock.calls[0][0] // to get first argument and so on
And then compare with equality.
Related
I am new to react , can i style inside a const
Here is my code
const initialStudentCounter =
'Students' + ' ' + JSON.stringify(studentCountInitial);
I need to style this "JSON.stringify(studentCountInitial)"
I tried this way , but didn't work
const initialStudentCounter =
'Students' + ' ' + <span style={color : 'red'}>JSON.stringify(studentCountInitial)</span> ;
Can some one help me please
You don't need JSON.stringify in JSX. You do need to insert this into your JSX with a surrounding element. You shouldn't need the + for concatenating strings anymore either.
export default function App() {
const studentCountInitial = 4;
const initialStudentCounter = <div>Students <span style={{color : 'red'}}>{studentCountInitial}</span></div>;
return (
initialStudentCounter
);
}
In React, you need to use double curly brackets (The first set of brackets is telling the compiler that this is Javascript, the second set is telling the compiler that this is an object.) when styling elements. Also, surround your element with a string.
Code:
const initialStudentCounter =
'Students' + ' ' + `<span style={{ color : 'red' }}>${JSON.stringify(studentCountInitial)}</span>`;
There are at least two problems here:
You're concatenating a string with a JSX element.
Your style attribute's value is missing a pair of curly braces for opening and closing an object (the first pair merely lets you use JS syntax).
const initialStudentCounter = <p>Students <span style={{color : 'red'}}>{JSON.stringify(studentCountInitial)}</span></p>;
I also suspect that studentCountInitial is just a plain number and thus it probably doesn't need to be stringified. So you can probably just do this...
const initialStudentCounter = <p>Students <span style={{color : 'red'}}>{studentCountInitial}</span></p>;
If you really need to then convert that JSX element to a string, you could use something like ReactDOMServer.renderToStaticMarkup(), but that's likely unnecessary.
Currently I have this test:
import toHoursMinutes from '../../../app/utils/toHoursMinutes';
describe('app.utils.toHoursMinutes', () => {
it('should remove 3rd group of a time string from date object', async () => {
expect(toHoursMinutes(new Date('2020-07-11T23:59:58.000Z'))).toBe('19:59');
});
});
What toHoursMinutes does is to receive a Date object and transform it like this way:
export default (date) => `${('' + date.getHours()).padStart(2, '0')}:${('' + date.getMinutes()).padStart(2, '0')}`;
My local time offset is -4 so my test pass ok if I compare 23:59 with 19:59, but I want to run the test anywhere, so I prefer to compare the output of toHoursMinutes() with a regex expression like this one, that check the hh:mm format: ^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$
But how can I use a regex to compare instead a explicit string?
I tried this:
const expected = [
expect.stringMatching(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/)
];
it.only('matches even if received contains additional elements', () => {
expect(['55:56']).toEqual(
expect.arrayContaining(expected)
);
});
But I get a:
Expected: ArrayContaining [StringMatching /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/]
Received: ["55:56"]
There is a toMatch function on expect() that does just that.
expect('12:59').toMatch(/^\d{1,2}:\d{2}$/); // stripped-down regex
https://jestjs.io/docs/expect#tomatchregexp--string
If you want to match a regex inside of other jest functions, you can do so by using expect.stringMatching(/regex/).
expect({
name: 'Peter Parker',
}).toHaveProperty('name', expect.stringMatching(/peter/i))
https://jestjs.io/docs/expect#expectstringmatchingstring--regexp
I was ok except in the dummy data because wasn't for the regex. In case anyone need it, this works:
const expected2 = [
expect.stringMatching(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/)
];
it('matches even if received contains additional elements', () => {
expect(['12:59']).toEqual(
expect.arrayContaining(expected2)
);
});
In my case, I can check the format of a time in a span using toHaveTextContent().
const span = screen.getByRole("presentation", { name: /time/i });
expect(span).toHaveTextContent(/^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$/);
Docs for toHaveTextContent(): https://github.com/testing-library/jest-dom#tohavetextcontent
I was wondering how to trim a file name in JS to show "..." or any appendix for that matter after a certain number of characters, the most efficient way to handle all possible test cases.
Rules
Show the actual file extension and not the last character after splitting the string name with "."
The function should take the input file name (string), the number of characters to trim (integer) and appendix (string) as the parameter.
By efficient, I mean I expect to write fewer lines of code and handle all possible edge cases.
Sample Inputs
myAwesomeFile.min.css
my Awesome File.tar.gz
file.png
Expected output (say I want to trim after 5 characters)
myAwe....min.css
my Aw....tar.gz
file.png
Editing the question to show my attempt
function trimFileName(str, noOfChars, appendix) {
let nameArray = str.split(".");
let fileType = `.${nameArray.pop()}`;
let fileName = nameArray.join(" ");
if (fileName.length >= noOfChars) {
fileName = fileName.substr(0, noOfChars) + appendix;
};
return (fileName + fileType);
}
console.log(trimFileName("myAwesomeFile.min.css", 5, "..."));
console.log(trimFileName("my Awesome File.tar.gz", 5, "..."));
console.log(trimFileName("file.png", 5, "..."));
Edit #2: Feel free to go ahead and edit the question if you think it's not the standard expectation and add more edge cases to the sample inputs and expected outputs.
Edit #3: Added a few more details to the question after the new comments. I know my attempt doesn't fulfill my expected outputs (and I am unsure whether the output I have listed above is a standard expectation or not).
Edit #4 (Final): Removed the rule of not breaking a word in the middle after a continuous backlash in the comments and changed the rules to cater to more realistic and practical use cases.
If we treat the dot character . as a separator for file extensions, what you ask for can be solved with a single line of JavaScript:
name.replace(new RegExp('(^[^\\.]{' + chars + '})[^\\.]+'), '$1' + subst);
Demo code in the following snippet:
function f(name, chars, subst) {
return name.replace(
new RegExp('(^[^\\.]{' + chars + '})[^\\.]+'), '$1' + subst);
}
test('myAwesomeFile.min.css', 5, '...', 'myAwe....min.css');
test('my Awesome File.tar.gz', 5, '...', 'my Aw....tar.gz');
test('file.png', 5, '...', 'file.png');
function test(filename, length, subst, expected) {
let actual = f(filename, length, subst);
console.log(actual, actual === expected ? 'OK' : 'expected: ' + expected);
}
On Windows, AFAIK, the file extension is only what follows the last dot. Thus, technically, the file extension of "myAwesomeFile.min.css" is just css, and the file extension of "my Awesome File.tar.gz" is just gz.
In this case, what you ask for can still be solved with one line of JavaScript:
name.replace(new RegExp('(^.{' + chars + '}).+(\\.[^\\.]*$)'), '$1' + subst + '$2');
Demo code in the following snippet:
function f(name, chars, subst) {
return name.replace(
new RegExp('(^.{' + chars + '}).+(\\.[^\\.]*$)'), '$1' + subst + '$2');
}
test('myAwesomeFile.min.css', 5, '...', 'myAwe....css');
test('my Awesome File.tar.gz', 5, '...', 'my Aw....gz');
test('file.png', 5, '...', 'file.png');
function test(filename, length, subst, expected) {
let actual = f(filename, length, subst);
console.log(actual, actual === expected ? 'OK' : 'expected: ' + expected);
}
If you really want to allow edge cases with specific multiple extensions, you probably need to define a comprehensive list of all allowed multiple extensions to know how to deal with cases like "my.awesome.file.min.css". You would need to provide a list of all cases you want to include before it would be possible to determine how efficient any solution could be.
It is really hard to account for all extensions (including edge cases). See this list for example for common extensions: https://www.computerhope.com/issues/ch001789.htm. Event with that many extensions, the list is exhaustive of all extensions.
Your function is OK but to account for more cases it could be re-written to this:
function trimFileName(filename, limit = 5, spacer = '.') {
const split = filename.indexOf(".");
const name = filename.substring(0, split);
const ext = filename.substring(split);
let trimName = name.substring(0, limit);
if (name.length > trimName.length)
trimName = trimName.padEnd(limit + 3, spacer);
return trimName + ext;
}
console.log(trimFileName("myAwesomeFile.min.css"));
console.log(trimFileName("my Awesome File.tar.gz"));
console.log(trimFileName("file.png"));
Below is a pretty simple approach to achieve shortening in the fashion you desire. Comments are in the code, but let me know if anything needs additional explanation:
//A simple object to hold some configs about how we want to shorten the file names
const config = {
charsBeforeTrim: 5,
seperator: '.',
replacementText: '....'
};
//Given file names to shorten
const files = ['myAwesomeFile.min.css', 'my Awesome File.tar.gz', 'file.png'];
//Function to do the actual file name shortening
const shorten = s =>
s.length > config.charsBeforeTrim ? `${s.substring(0, config.charsBeforeTrim)}${config.replacementText}` : s;
//Function to generate a short file name with extension(s)
const generateShortName = (file, config) => {
//ES6 Array destructuring makes it easy to get the file name in a unique variable while keeping the remaining elements (the extensions) in their own array:
const [name, ...extensions] = file.split(config.seperator);
//Simply append all remaining extension chunks to the shortName
return extensions.reduce((accum, extension) => {
accum += `${config.seperator}${extension}`;
return accum;
}, shorten(name));
};
//Demonstrate usage
const shortFileNames = files.map(file => generateShortName(file, config));
console.log(shortFileNames);
const parse = (filename, extIdx = filename.lastIndexOf('.')) => ({
name: filename.substring(0, extIdx),
extension: filename.substring(extIdx + 1),
})
const trimFileName = (
filename, size = 5, fill = '...',
file = parse(filename),
head = file.name.substring(0, size)
) => file.name.length >= size ? `${head}${fill}${file.extension}` : filename
/* - - - - - - - - - - - - - - - - - - - - - - - - - */
;[
'myAwesomeFile.min.css',
'my.Awesome.File.min.css',
'my Awesome File.tar.gz',
'file.png',
].forEach(f => console.log(trimFileName(f)))
You can fairly straightforwardly pull the your extension condition (easily replaced with a list of valid extensions) and regex pull the last part. Then you just add a check on the filename (starting at the beginning of filename) to trim the result.
const trim = (string, x) => {
// We assume that up to last two . delimited substrings of length are the extension
const extensionRegex = /(?:(\.[a-zA-Z0-9]+){0,2})$/g;
const { index } = extensionRegex.exec(string);
// No point in trimming since result string would be longer than input
if (index + 2 < x) {
return string;
}
return string.substr(0, x) + ".." + string.substr(index);
};
/* Assert that we keep the extension */
console.log("cat.tar.gz", trim("cat.tar.gz", 100) == "cat.tar.gz");
console.log("cat.zip", trim("cat.zip", 100) == "cat.zip");
/* Assert that we keep x characters before trim */
console.log("1234567890cat.tar.gz",!trim("1234567890cat.tar.gz",10).includes("cat"));
console.log("1234567890cat.zip", !trim("1234567890cat.zip", 10).includes("cat"));
I understand the syntax of ES6 tagged templates. What I don't see is the practical usability. When is it better than passing an object parameter, like the settings in jQuery's AJAX? $.ajax('url', { /*this guy here*/ })
Right now I only see the tricky syntax but I don't see why I would need/use it. I also found that the TypeScript team chose to implement it (in 1.5) before other important features. What is the concept behind tagged string templates?
You can use tagged templates to build APIs that are more expressive than regular function calls.
For example, I'm working on a proof-of-concept library for SQL queries on JS arrays:
let admins = sql`SELECT name, id FROM ${users}
WHERE ${user => user.roles.indexOf('admin') >= 0}`
Notice it has nothing to do with String interpolation; it uses tagged templates for readability. It would be hard to construct something that reads as intuitively with plain function calls - I guess you'd have something like this:
let admins = sql("SELECT name, id FROM $users WHERE $filter",
{ $users: users, $filter: (user) => user.roles.contains('admin') })
This example is just a fun side project, but I think it shows some of the benefits of tagged templates.
Another example, maybe more obvious, is i18n - a tagged template could insert locale-sensitive versions of your input.
See Sitepoint's explanation:
The final stage of template strings specification is about adding a custom function before the string itself to create a tagged template string.
...
For instance, here is a piece of code to block strings that try to inject custom DOM elements:
var items = [];
items.push("banana");
items.push("tomato");
items.push("light saber");
var total = "Trying to hijack your site <BR>";
var myTagFunction = function (strings,...values) {
var output = "";
for (var index = 0; index < values.length; index++) {
var valueString = values[index].toString();
if (valueString.indexOf(">") !== -1) {
// Far more complex tests can be implemented here :)
return "String analyzed and refused!";
}
output += strings[index] + values[index];
}
output += strings[index]
return output;
}
result.innerHTML = myTagFunction `You have ${items.length} item(s) in your basket for a total of $${total}`;
Tagged template strings can used for a lot of things like security, localization, creating your own domain specific language, etc.
They're useful because the function can (almost) completely define the meaning of the text inside it (almost = other than placeholders). I like to use the example of Steven Levithan's XRegExp library. It's awkward to use regular expressions defined as strings, because you have to double-escape things: Once for the string literal, and once for regex. This is one of the reasons we have regular expression literals in JavaScript.
For instance, suppose I'm doing maintenance on a site and I find this:
var isSingleUnicodeWord = /^\w+$/;
...which is meant to check if a string contains only "letters." Two problems: A) There are thousands of "word" characters across the realm of human language that \w doesn't recognize, because its definition is English-centric; and B) It includes _, which many (including the Unicode consortium) would argue is not a "letter."
Suppose in my work I've introduced XRegExp to the codebase. Since I know it supports \pL (\p for Unicode categories, and L for "letter"), I might quickly swap this in:
var isSingleUnicodeWord = XRegExp("^\pL+$"); // WRONG
Then I wonder why it didn't work, *facepalm*, and go back and escape that backslash, since it's being consumed by the string literal:
var isSingleUnicodeWord = XRegExp("^\\pL+$");
// ---------------------------------^
What a pain. Suppose I could write the actual regular expression without worrying about double-escaping?
I can: With a tagged template function. I can put this in my standard lib:
function xrex(strings, ...values) {
const raw = strings.raw;
let result = "";
for (let i = 0; i < raw.length; ++i) {
result += raw[i];
if (i < values.length) { // `values` always has one fewer entry
result += values[i];
}
}
return XRegExp(result);
}
Or alternately, this is a valid use case for reduce, and we can use destructuring in the argument list:
function xrex({raw}, ...values) {
return XRegExp(
raw.reduce(
(acc, str, index) => acc + str + (index < values.length ? values[index] : ""),
""
)
);
}
And then I can happily write:
const isSingleUnicodeWord = xrex`^\pL+$`;
Example:
// My tag function (defined once, then reused)
function xrex({raw}, ...values) {
const result = raw.reduce(
(acc, str, index) => acc + str + (index < values.length ? values[index] : ""),
""
);
console.log("Creating with:", result);
return XRegExp(result);
}
// Using it, with a couple of substitutions to prove to myself they work
let category = "L"; // L: Letter
let maybeEol = "$";
let isSingleUnicodeWord = xrex`^\p${category}+${maybeEol}`;
function test(str) {
console.log(str + ": " + isSingleUnicodeWord.test(str));
}
test("Русский"); // true
test("日本語"); // true
test("العربية"); // true
test("foo bar"); // false
test("$£"); // false
<script src="https://cdnjs.cloudflare.com/ajax/libs/xregexp/3.2.0/xregexp-all.min.js"></script>
The only thing I have to remember now is that ${...} is special because it's a placeholder. In this specific case, it's not a problem, I'm unlikely to want to apply a quantifier to the end-of-input assertion, but that's a coincidence...
I understand the syntax of ES6 tagged templates. What I don't see is the practical usability. When is it better than passing an object parameter, like the settings in jQuery's AJAX? $.ajax('url', { /*this guy here*/ })
Right now I only see the tricky syntax but I don't see why I would need/use it. I also found that the TypeScript team chose to implement it (in 1.5) before other important features. What is the concept behind tagged string templates?
You can use tagged templates to build APIs that are more expressive than regular function calls.
For example, I'm working on a proof-of-concept library for SQL queries on JS arrays:
let admins = sql`SELECT name, id FROM ${users}
WHERE ${user => user.roles.indexOf('admin') >= 0}`
Notice it has nothing to do with String interpolation; it uses tagged templates for readability. It would be hard to construct something that reads as intuitively with plain function calls - I guess you'd have something like this:
let admins = sql("SELECT name, id FROM $users WHERE $filter",
{ $users: users, $filter: (user) => user.roles.contains('admin') })
This example is just a fun side project, but I think it shows some of the benefits of tagged templates.
Another example, maybe more obvious, is i18n - a tagged template could insert locale-sensitive versions of your input.
See Sitepoint's explanation:
The final stage of template strings specification is about adding a custom function before the string itself to create a tagged template string.
...
For instance, here is a piece of code to block strings that try to inject custom DOM elements:
var items = [];
items.push("banana");
items.push("tomato");
items.push("light saber");
var total = "Trying to hijack your site <BR>";
var myTagFunction = function (strings,...values) {
var output = "";
for (var index = 0; index < values.length; index++) {
var valueString = values[index].toString();
if (valueString.indexOf(">") !== -1) {
// Far more complex tests can be implemented here :)
return "String analyzed and refused!";
}
output += strings[index] + values[index];
}
output += strings[index]
return output;
}
result.innerHTML = myTagFunction `You have ${items.length} item(s) in your basket for a total of $${total}`;
Tagged template strings can used for a lot of things like security, localization, creating your own domain specific language, etc.
They're useful because the function can (almost) completely define the meaning of the text inside it (almost = other than placeholders). I like to use the example of Steven Levithan's XRegExp library. It's awkward to use regular expressions defined as strings, because you have to double-escape things: Once for the string literal, and once for regex. This is one of the reasons we have regular expression literals in JavaScript.
For instance, suppose I'm doing maintenance on a site and I find this:
var isSingleUnicodeWord = /^\w+$/;
...which is meant to check if a string contains only "letters." Two problems: A) There are thousands of "word" characters across the realm of human language that \w doesn't recognize, because its definition is English-centric; and B) It includes _, which many (including the Unicode consortium) would argue is not a "letter."
Suppose in my work I've introduced XRegExp to the codebase. Since I know it supports \pL (\p for Unicode categories, and L for "letter"), I might quickly swap this in:
var isSingleUnicodeWord = XRegExp("^\pL+$"); // WRONG
Then I wonder why it didn't work, *facepalm*, and go back and escape that backslash, since it's being consumed by the string literal:
var isSingleUnicodeWord = XRegExp("^\\pL+$");
// ---------------------------------^
What a pain. Suppose I could write the actual regular expression without worrying about double-escaping?
I can: With a tagged template function. I can put this in my standard lib:
function xrex(strings, ...values) {
const raw = strings.raw;
let result = "";
for (let i = 0; i < raw.length; ++i) {
result += raw[i];
if (i < values.length) { // `values` always has one fewer entry
result += values[i];
}
}
return XRegExp(result);
}
Or alternately, this is a valid use case for reduce, and we can use destructuring in the argument list:
function xrex({raw}, ...values) {
return XRegExp(
raw.reduce(
(acc, str, index) => acc + str + (index < values.length ? values[index] : ""),
""
)
);
}
And then I can happily write:
const isSingleUnicodeWord = xrex`^\pL+$`;
Example:
// My tag function (defined once, then reused)
function xrex({raw}, ...values) {
const result = raw.reduce(
(acc, str, index) => acc + str + (index < values.length ? values[index] : ""),
""
);
console.log("Creating with:", result);
return XRegExp(result);
}
// Using it, with a couple of substitutions to prove to myself they work
let category = "L"; // L: Letter
let maybeEol = "$";
let isSingleUnicodeWord = xrex`^\p${category}+${maybeEol}`;
function test(str) {
console.log(str + ": " + isSingleUnicodeWord.test(str));
}
test("Русский"); // true
test("日本語"); // true
test("العربية"); // true
test("foo bar"); // false
test("$£"); // false
<script src="https://cdnjs.cloudflare.com/ajax/libs/xregexp/3.2.0/xregexp-all.min.js"></script>
The only thing I have to remember now is that ${...} is special because it's a placeholder. In this specific case, it's not a problem, I'm unlikely to want to apply a quantifier to the end-of-input assertion, but that's a coincidence...