diff --git a/katas/es1/language/unary-operators/all.js b/katas/es1/language/unary-operators/all.js index a00e950..905a40b 100644 --- a/katas/es1/language/unary-operators/all.js +++ b/katas/es1/language/unary-operators/all.js @@ -26,7 +26,7 @@ describe('All unary operators', () => { var trueString = !'true'; assert.strictEqual(!trueString, false); }); - it('AND it is `delete` before an object`s property THEN the property is removed', () => { + it('AND it is `delete` before an object\'s property THEN the property is removed', () => { var obj = {x: 1}; delete obj.axe; assert.strictEqual(obj.x, undefined); @@ -35,7 +35,7 @@ describe('All unary operators', () => { var toUndefined = delete {any: 'thing'}; assert.strictEqual(toUndefined, undefined); }); - it('AND it is `typeof` THEN it returns the operand`s type', () => { + it('AND it is `typeof` THEN it returns the operand\'s type', () => { var whatsTheType = typeof '42'; assert.strictEqual(whatsTheType, 'number'); }); diff --git a/katas/es1/language/unary-operators/plus-in-depth.js b/katas/es1/language/unary-operators/plus-in-depth.js index 08023ae..8e31ee1 100644 --- a/katas/es1/language/unary-operators/plus-in-depth.js +++ b/katas/es1/language/unary-operators/plus-in-depth.js @@ -105,7 +105,7 @@ describe('Unary "+" operator', () => { var Class = class{toString = () => '123'}; assert.strictEqual(+new Class, 23); }); - it('AND its a class with `valueOf()` and `toString()` THEN `valueOf`s value is used', () => { + it('AND its a class with `valueOf()` and `toString()` THEN `valueOf`\'s value is used', () => { var toString = () => '23'; var valueOf = toString; assert.strictEqual(+new class{toString = toString; valueOf = valueOf}, 42); diff --git a/katas/es6/language/__all__.json b/katas/es6/language/__all__.json index f699a65..b20841b 100644 --- a/katas/es6/language/__all__.json +++ b/katas/es6/language/__all__.json @@ -88,9 +88,9 @@ }, { "name": "`raw` property", - "description": "The `raw` property accesses the string as it was entered.", + "description": "The `raw` property can access the string without processing escape sequences.", "path": "template-strings/raw", - "level": "INTERMEDIATE", + "level": "EXPERT", "requiresKnowledgeFrom": [ { "bundle": "es6/language", @@ -99,6 +99,10 @@ { "bundle": "es6/language", "id": 3 + }, + { + "bundle": "es6/language", + "id": 82 } ], "links": [ @@ -116,6 +120,13 @@ "tags": [ "spec" ] + }, + { + "url": "https://tc39.es/ecma262/#sec-gettemplateobject", + "comment": "The JS engine internal abstract operation `GetTemplateObject` describes how the template object, that is passed to the tag-function is created, see esp. the adding of the `raw` property onto the array here (item: 14. Perform ! DefinePropertyOrThrow(template, \"raw\"...).", + "tags": [ + "spec" + ] } ], "groupName": "Template strings", @@ -2294,6 +2305,58 @@ "groupNameSlug": "number-api", "publishDateRfc822": "Sun, 06 Oct 2019 17:29:00 GMT", "id": 81 + }, + { + "name": "`String.raw`", + "description": "`String.raw` is a tag function for a template literal, which returns the raw string, e.g. \\n (not \n) for a line break.", + "path": "string-api/raw", + "level": "ADVANCED", + "requiresKnowledgeFrom": [ + { + "bundle": "es6/language", + "id": 8 + }, + { + "bundle": "es6/language", + "id": 5 + }, + { + "bundle": "es6/language", + "id": 18 + }, + { + "bundle": "es6/language", + "id": 20 + } + ], + "links": [ + { + "url": "https://262.ecma-international.org/6.0/#sec-string.raw", + "comment": "The original official specification when introduced, it does not explain what this function does, but very much in detail how it works which results in explaining the \"what\" too.", + "tags": [ + "spec" + ] + }, + { + "url": "https://tc39.es/ecma262/#sec-string.raw", + "comment": "The more readable and usable specification, in the version of the continuously maintained specification document.", + "tags": [ + "spec" + ] + }, + { + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw", + "comment": "The Mozilla Developer Network docs, an easy to understand explanation.", + "tags": [ + "mdn", + "docs" + ] + } + ], + "groupName": "String API", + "groupNameSlug": "string-api", + "publishDateRfc822": "Sun, 08 Oct 2023 19:15:00 GMT", + "id": 82 } ] } \ No newline at end of file diff --git a/katas/es6/language/__grouped__.json b/katas/es6/language/__grouped__.json index f6e0f83..8f1a216 100644 --- a/katas/es6/language/__grouped__.json +++ b/katas/es6/language/__grouped__.json @@ -87,9 +87,9 @@ }, { "name": "`raw` property", - "description": "The `raw` property accesses the string as it was entered.", + "description": "The `raw` property can access the string without processing escape sequences.", "path": "template-strings/raw", - "level": "INTERMEDIATE", + "level": "EXPERT", "requiresKnowledgeFrom": [ { "bundle": "es6/language", @@ -98,6 +98,10 @@ { "bundle": "es6/language", "id": 3 + }, + { + "bundle": "es6/language", + "id": 82 } ], "publishDateUTC": "2015-03-18T07:55:00.000Z", @@ -116,6 +120,13 @@ "tags": [ "spec" ] + }, + { + "url": "https://tc39.es/ecma262/#sec-gettemplateobject", + "comment": "The JS engine internal abstract operation `GetTemplateObject` describes how the template object, that is passed to the tag-function is created, see esp. the adding of the `raw` property onto the array here (item: 14. Perform ! DefinePropertyOrThrow(template, \"raw\"...).", + "tags": [ + "spec" + ] } ], "groupName": "Template strings", @@ -2103,6 +2114,57 @@ ], "groupName": "String API", "id": "74" + }, + { + "name": "`String.raw`", + "description": "`String.raw` is a tag function for a template literal, which returns the raw string, e.g. \\n (not \n) for a line break.", + "path": "string-api/raw", + "level": "ADVANCED", + "requiresKnowledgeFrom": [ + { + "bundle": "es6/language", + "id": 8 + }, + { + "bundle": "es6/language", + "id": 5 + }, + { + "bundle": "es6/language", + "id": 18 + }, + { + "bundle": "es6/language", + "id": 20 + } + ], + "publishDateUTC": "2023-10-08T19:15:00.000Z", + "links": [ + { + "url": "https://262.ecma-international.org/6.0/#sec-string.raw", + "comment": "The original official specification when introduced, it does not explain what this function does, but very much in detail how it works which results in explaining the \"what\" too.", + "tags": [ + "spec" + ] + }, + { + "url": "https://tc39.es/ecma262/#sec-string.raw", + "comment": "The more readable and usable specification, in the version of the continuously maintained specification document.", + "tags": [ + "spec" + ] + }, + { + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw", + "comment": "The Mozilla Developer Network docs, an easy to understand explanation.", + "tags": [ + "mdn", + "docs" + ] + } + ], + "groupName": "String API", + "id": "82" } ], "slug": "string-api", diff --git a/katas/es6/language/map/set.js b/katas/es6/language/map/set.js index 128c157..854696c 100644 --- a/katas/es6/language/map/set.js +++ b/katas/es6/language/map/set.js @@ -21,7 +21,7 @@ describe('`Map.prototype.set` adds a new element with key and value to a Map', f map.set('key', 'value3'); assert.equal(map.get('key'), 'value1'); }); - it('`set()` returns the map object, it`s chainable', function() { + it('`set()` returns the map object, it\'s chainable', function() { let map = new Map(); map.set(1, 'one') .set(2, 'two') diff --git a/katas/es6/language/string-api/includes.js b/katas/es6/language/string-api/includes.js index fa70736..189c8d7 100644 --- a/katas/es6/language/string-api/includes.js +++ b/katas/es6/language/string-api/includes.js @@ -3,80 +3,84 @@ // Follow the hints of the failure messages! -describe('`string.includes()` determines if a string can be found inside another one', function() { - describe('finding a single character', function() { - it('can be done (a character is also a string, in JS)', function() { +describe('`string.includes()` finds one string inside another', function() { + describe('GIVEN searching a single character', function() { + it('WHEN "x" is found THEN then `s.includes("x")` returns true', function() { const searchString = 'a'; assert.equal('xyz'.includes(searchString), true); }); - it('reports false if character was not found', function() { + it('WHEN the searched character is not contained THEN it returns false', function() { const actual = '???'; assert.equal(actual, 'xyz'.includes('abc')); }); }); - describe('find a string', function() { - it('that matches exactly', function() { + describe('GIVEN searching for a string', function() { + it('WHEN the searched string matches exactly THEN `true` is returned', function() { const findSome = findMe => 'xyz'.includes; assert.equal(findSome('xyz'), true); }); + it('WHEN searching for "A" THEN "a" is not found, the search is case-sensitive', function() { + const findInAbc = (what) => 'abc'.inkludez(what); + assert.equal(findInAbc('A'), false); + }); }); - describe('search for an empty string, is always true', function() { - it('in an empty string', function() { + describe('GIVEN the searched string is an empty string `""` it is always found', function() { + it('WHEN the searching in an empty string THEN this returns `true`', function() { const emptyString = ' '; assert.equal(''.includes(emptyString), true); }); - it('in `abc`', function() { + it('WHEN the searched string is NOT empty THEN `s.includes("")` returns true', function() { const actual = _.includes(''); assert.equal(actual, true); }); }); - describe('special/corner cases', function() { - it('search for `undefined` in a string fails', function() { - const findInAbc = (what) => 'abc'.includes; - assert.equal(findInAbc(undefined), false); - }); - it('searches are case-sensitive', function() { - const findInAbc = (what) => 'abc'.inkludez(what); - assert.equal(findInAbc('A'), false); - }); - it('must NOT be a regular expression', function() { - const regExp = ''; - assert.throws(() => {''.includes(regExp)}); - }); - describe('coerces the searched "thing" into a string', function() { - it('e.g. from a number', function() { + describe('GIVEN we use not only strings', function() { + describe('JavaScript tries to coerce (convert) the parameter to a string', function() { + it('WHEN searching for the number `4` THEN it is found in a string that contains it', function() { const actual = '123'.includes(4); assert.equal(actual, true); }); - it('e.g. from an array', function() { + it('WHEN searching for an array THEN the array is coerced to a string first', function() { const actual = '123'.includes([1,2,3]); assert.equal(actual, true); }); - it('e.g. from an object, with a `toString()` method', function() { + it('WHEN searching with an object that has a `toString()` method THEN the result of that function call is used as search string', function() { const objWithToString = {toString: 1}; assert.equal('123'.includes(objWithToString), true); }); }); + describe('some searches you should prevent', () => { + it('WHEN searching for `undefined` in a string THEN this returns `false`', function() { + const findInAbc = (what) => 'abc'.includes; + assert.equal(findInAbc(undefined), false); + }); + it('WHEN the search parameter is a regular expression THEN `includes()` throws an error', function() { + const regExp = ''; + assert.throws(() => { + ''.includes(regExp); + }); + }); + }); }); - describe('takes a position from where to start searching', function() { - it('does not find `a` after position 1 in `abc`', function() { + describe('GIVEN a 1st parameter, the position where to start searching from', function() { + it('WHEN given `1` THEN it does not find `a` after position 1 in `abc`', function() { const position = 0; assert.equal('abc'.includes('a', position), false); }); - it('even the position gets coerced', function() { + it('WHEN the position is given as a string THEN it gets coerced to a number', function() { const findAtPosition = position => 'xyz'.includes('x', pos); assert.equal(findAtPosition('2'), false); }); - describe('invalid positions get converted to 0', function() { - it('e.g. `undefined`', function() { + describe('GIVEN an invalid positions parameter, it gets converted to 0', function() { + it('WHEN passing `undefined` THEN the search starts at position 0', function() { const findAtPosition = (pos=2) => 'xyz'.includes('x', pos); assert.equal(findAtPosition(undefined), true); }); - it('negative numbers', function() { + it('WHEN given a negative numbers THEN the search starts at position 0', function() { const findAtPosition = (pos) => 'xyz'.includes('x', -pos); assert.equal(findAtPosition(-2), true); }); - it('NaN', function() { + it('WHEN given `NaN` THEN the search starts at position 0', function() { const findAtPosition = (pos) => 'xyz'.includes('x', 1); assert.equal(findAtPosition(NaN), true); }); diff --git a/katas/es6/language/string-api/raw.js b/katas/es6/language/string-api/raw.js new file mode 100644 index 0000000..1732ff8 --- /dev/null +++ b/katas/es6/language/string-api/raw.js @@ -0,0 +1,41 @@ +import assert from "assert"; + +describe('`String.raw` provides the raw string of a template literal', function() { + describe('GIVEN using `String.raw` as a tag-function', () => { + it('WHEN passing line-break (\\n) THEN the `String.raw` of it escapes the backslash and makes it "visible"', function() { + var expected = '\n'; + assert.equal(String.raw`\n`, expected); + }); + it('WHEN passing an escaped backslash to `String.raw` THEN this equals a string with two backslashes, where each is escaped', function() { + const TWO_BACKSLASHES = '\\'; + assert.equal(String.raw`\\`, TWO_BACKSLASHES); + }); + it('WHEN passing a unicode character THEN the leading backslash is made "visible"', function() { + var rawSmilie = '\u{1F600}'; + const actual = String.raw`\u{1F600}`; + assert.equal(actual, rawSmilie); + }); + }); + describe('GIVEN using `String.raw` as a function to call', () => { + it('WHEN `String.raw()` is called without a parameter THEN it throws', () => { + const callingStringRaw = () => String.raw; + assert.throws(callingStringRaw, TypeError); + }); + it('WHEN `String.raw()` is called with a string as parameter THEN it still throws', () => { + const callRawWithAString = () => String.raw({raw: 'a string'}); + assert.throws(callRawWithAString, TypeError); + }); + it('WHEN passing the first parameter `{raw: []}` THEN this equals to an empty string', () => { + const firstParam = {raw: undefined}; + assert.equal(String.raw(firstParam), ''); + }); + it('WHEN passing the first property `raw` an array of strings THEN they are just concatenated', () => { + const expected = 'a,b,c'; + assert.equal(String.raw({raw: ['a', 'b', 'c']}), expected); + }); + it('WHEN passing more parameters, the substitutions THEN these are used as the filler between each raw string, in their according place', () => { + const rawStrings = ['-', '-']; + assert.equal(String.raw({raw: rawStrings}, 1, 2), '.1-2.'); + }); + }); +}); diff --git a/katas/es6/language/template-strings/basics.js b/katas/es6/language/template-strings/basics.js index 7ab05b2..bf79708 100644 --- a/katas/es6/language/template-strings/basics.js +++ b/katas/es6/language/template-strings/basics.js @@ -3,21 +3,19 @@ // Follow the hints of the failure messages! -describe('A template string, is wrapped in ` (backticks) instead of \' or "', function() { - describe('by default, behaves like a normal string', function() { - it('just surrounded by backticks', function() { - var str = ``; - assert.equal(str, 'like a string'); - }); +describe('Use backticks `` ` `` for template strings (not quotes `` \' `` or `` " ``)', function() { + it('WHEN you write a string in backticks THEN it behaves just like a normal string', function() { + var str = ``; + assert.equal(str, 'like a string'); }); - describe('can evaluate variables, which are wrapped in "${" and "}"', function() { - it('e.g. a simple variable "${x}" just gets evaluated', function() { + describe('GIVEN variables wrapped in `${` and `}`', function() { + it('WHEN using `${x}` inside a template string THEN the value of `x` is written out instead', function() { var x = 42; var evaluated = `x=#x`; assert.equal(evaluated, 'x=' + x); }); - it('multiple variables get evaluated too', function() { + it('WHEN using multiple variables THEN they get evaluated too', function() { var x = 42; var y = 23; var evaluated = '${ x } + $ { y }'; @@ -25,14 +23,14 @@ describe('A template string, is wrapped in ` (backticks) instead of \' or "', fu }); }); - describe('can evaluate any expression, wrapped inside "${...}"', function() { - it('all inside "${...}" gets evaluated', function() { + describe('GIVEN expressions wrapped inside `${...}`', function() { + it('WHEN wrapping an expression in `${...}` THEN they get evaluated', function() { var x = 42; var y = 23; var evaluated = `${ x } + ${ y }`; assert.equal(evaluated, x+y); }); - it('inside "${...}" can also be a function call', function() { + it('WHEN a function call is inside `${...}` THEN the result is rendered', function() { function getEnv(){ return 'ECMAScript'; } diff --git a/katas/es6/language/template-strings/multiline.js b/katas/es6/language/template-strings/multiline.js index dcc2853..5220fb6 100644 --- a/katas/es6/language/template-strings/multiline.js +++ b/katas/es6/language/template-strings/multiline.js @@ -4,25 +4,23 @@ describe('Template string, can contain multiline content', function() { - it('wrap it in backticks (`) and add a newline, to span across two lines', function() { - var normalString = `line1 //// line3`; + it('WHEN a new line is inside the backticks `` ` `` THEN the string can span across many lines', function() { + var normalString = ` - assert.equal(normalString, 'line1\n\nline3'); +line1 +line2 +line3 +`; + assert.equal(normalString, '\nline1\nline2\nline3\n'); }); - it('even over more than two lines', function() { - var multiline = ``; - - - assert.equal(multiline.split('\n').length, 4); - }); - describe('and expressions inside work too', function() { - it('like simple variables', function() { + describe('GIVEN expressions inside of a template string', function() { + it('WHEN a simple variable is on the third line THEN it is also evaluated', function() { var x = 42; var multiline = `line 1 $ {x}`; assert.equal(multiline, 'line 1\n\n 42'); }); - it('also here spaces matter', function() { + it('AND spaces matter', function() { var x = 42; var multiline = ``; diff --git a/katas/es6/language/template-strings/raw.js b/katas/es6/language/template-strings/raw.js index 2632d4e..732b2bf 100644 --- a/katas/es6/language/template-strings/raw.js +++ b/katas/es6/language/template-strings/raw.js @@ -3,34 +3,50 @@ // Follow the hints of the failure messages! -describe('Use the `raw` property of tagged template strings like so `s.raw`', function() { - it('the `raw` property accesses the string as it was entered', function() { - function firstChar(strings) { - return strings; - } - assert.equal(firstChar`\n`, '\\n'); - }); - it('`raw` can access the backslash of a line-break', function() { - function firstCharEntered(strings) { - var lineBreak = strings.raw; - return lineBreak; - } - assert.equal(firstCharEntered`\n`, '\\'); - }); - describe('`String.raw` as a static function', function(){ - it('concats the raw strings', function() { - var expected = '\n'; - assert.equal(String.raw`\n`, expected); +describe('Using `raw` property in a tagged template function', function() { + describe('What? The property `raw` is on the array that contains all string parts of a template string', () => { + it('WHEN reading `.raw` of the strings (the 1st parameter) THEN it returns an array of all raw strings of the template', () => { + // `strings` contains only the pure strings, not the expressions! + // This means the ${2} and ${3} are not contained in `strings`. + function tag(strings) { + return strings.___; + } + assert.deepEqual(tag`1${2}\n${3}4`, ['1', '\\n', '4']); }); - it('two raw-templates-string-backslashes equal two escaped backslashes', function() { - const TWO_BACKSLASHES = '\\'; - assert.equal(String.raw`\\`, TWO_BACKSLASHES); + it('WHEN reading the `raw` value of a backslash THEN this returns the text as written in the original string, not the character only', () => { + function tag(strings) { + return {string: strings[0], rawString: strings[0]}; + } + // Note: the four backslashes below are needed in order to escape each backslash, so \ in a string must always be \\ + // otherwise it is seen as a character to escape the following character, see e.g. \n which is a line break, without the + // leading \ it would just be an n. + assert.deepEqual(tag`\\`, {string: '\\', rawString: '\\\\'}); }); - it('works on unicodes too', function() { - var smilie = '\u{1F600}'; - var actual = String.raw`\u{1F600}`; - assert.equal(actual, smilie); + it('WHEN analyzing the tag-function arguments THEN the function signature is much like the one of `String.raw`', () => { + //: {"jskatas":{"terms": ["function signature", "tag-function"]}} + function tag({raw}, ...values) { + return [{raw}, ...values]; + } + + const signature = [{raw: ['one', '\\n']}, 0]; + assert.deepStrictEqual(tag`one${0}\n`, signature); + assert.deepStrictEqual(String.raw(...signature), 'one0\\n'); }); }); -}); + describe('using `raw` in a tagged template', () => { + it('WHEN using `.raw` on a "normal" character THEN this is the character itself', () => { + function tagged(string) { + return string.raw[0][0]; + } + assert.equal(tagged`AB`, 'B'); + }); + it('WHEN reading the first character of the `raw` value of a line break THEN this is a backslash', function() { + function firstCharEntered(strings) { + var lineBreak = strings.raw; + return lineBreak; + } + assert.equal(firstCharEntered`\n`, '\\'); + }); + }); +}); diff --git a/katas/es6/language/template-strings/tagged.js b/katas/es6/language/template-strings/tagged.js index 85594f9..eed541e 100644 --- a/katas/es6/language/template-strings/tagged.js +++ b/katas/es6/language/template-strings/tagged.js @@ -4,7 +4,7 @@ describe('Tagged template strings, are an advanced form of template strings', function() { - it('syntax: prefix a template string with a function to call (without "()" around it)', function() { + it('Syntax: prefix a template string with a function to call (without "()" around it)', function() { function tagFunction(s) { return s.toString(); }