From fb83181efbdb7d30d20720b7ce40530c438386b8 Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Fri, 10 Jun 2022 17:33:57 +0200 Subject: [PATCH 01/13] update support message in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d6831f3..95a69d8e 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]') // → 'hover:bg-dark-red p-3 bg-[#B91C1C]' ``` -- Supports Tailwind v3.0 (if you use Tailwind v2, use [tailwind-merge v0.9.0](https://github.com/dcastil/tailwind-merge/tree/v0.9.0)) +- Supports Tailwind v3.0 up to v3.1 (if you use Tailwind v2, use [tailwind-merge v0.9.0](https://github.com/dcastil/tailwind-merge/tree/v0.9.0)) - Works in Node >=12 and all modern browsers - Fully typed - [Check bundle size on Bundlephobia](https://bundlephobia.com/package/tailwind-merge) From fe77f2bf9537f731fe5d2f2a4a3880290990d58f Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Fri, 10 Jun 2022 17:46:40 +0200 Subject: [PATCH 02/13] add text-start amd text-end utilities --- src/lib/default-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/default-config.ts b/src/lib/default-config.ts index df98ea3e..ad73a3a8 100644 --- a/src/lib/default-config.ts +++ b/src/lib/default-config.ts @@ -689,7 +689,7 @@ export function getDefaultConfig() { * Text Alignment * @see https://tailwindcss.com/docs/text-align */ - 'text-alignment': [{ text: ['left', 'center', 'right', 'justify'] }], + 'text-alignment': [{ text: ['left', 'center', 'right', 'justify', 'start', 'end'] }], /** * Text Color * @see https://tailwindcss.com/docs/text-color From 8e589251eaaafb62f0479865bd3742584334c174 Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Fri, 10 Jun 2022 18:08:31 +0200 Subject: [PATCH 03/13] add border-spacing utilities --- src/lib/default-config.ts | 18 ++++++++++++++++++ tests/class-map.test.ts | 3 +++ 2 files changed, 21 insertions(+) diff --git a/src/lib/default-config.ts b/src/lib/default-config.ts index ad73a3a8..bac832bc 100644 --- a/src/lib/default-config.ts +++ b/src/lib/default-config.ts @@ -20,6 +20,7 @@ export function getDefaultConfig() { const brightness = fromTheme('brightness') const borderColor = fromTheme('borderColor') const borderRadius = fromTheme('borderRadius') + const borderSpacing = fromTheme('borderSpacing') const borderWidth = fromTheme('borderWidth') const contrast = fromTheme('contrast') const grayscale = fromTheme('grayscale') @@ -89,6 +90,7 @@ export function getDefaultConfig() { brightness: [isInteger], borderColor: [colors], borderRadius: ['none', '', 'full', isTshirtSize, isArbitraryLength], + borderSpacing: [spacing], borderWidth: getLengthWithEmpty(), contrast: [isInteger], grayscale: getZeroAndEmpty(), @@ -1190,6 +1192,21 @@ export function getDefaultConfig() { * @see https://tailwindcss.com/docs/border-collapse */ 'border-collapse': [{ border: ['collapse', 'separate'] }], + /** + * Border Spacing + * @see https://tailwindcss.com/docs/border-spacing + */ + 'border-spacing': [{ 'border-spacing': [borderSpacing] }], + /** + * Border Spacing X + * @see https://tailwindcss.com/docs/border-spacing + */ + 'border-spacing-x': [{ 'border-spacing-x': [borderSpacing] }], + /** + * Border Spacing Y + * @see https://tailwindcss.com/docs/border-spacing + */ + 'border-spacing-y': [{ 'border-spacing-y': [borderSpacing] }], /** * Table Layout * @see https://tailwindcss.com/docs/table-layout @@ -1561,6 +1578,7 @@ export function getDefaultConfig() { 'rounded-r': ['rounded-tr', 'rounded-br'], 'rounded-b': ['rounded-br', 'rounded-bl'], 'rounded-l': ['rounded-tl', 'rounded-bl'], + 'border-spacing': ['border-spacing-x', 'border-spacing-y'], 'border-w': ['border-w-t', 'border-w-r', 'border-w-b', 'border-w-l'], 'border-w-x': ['border-w-r', 'border-w-l'], 'border-w-y': ['border-w-t', 'border-w-b'], diff --git a/tests/class-map.test.ts b/tests/class-map.test.ts index 085115bf..340349e5 100644 --- a/tests/class-map.test.ts +++ b/tests/class-map.test.ts @@ -61,6 +61,9 @@ test('class map has correct class groups at first part', () => { 'border-color-x', 'border-color-y', 'border-opacity', + 'border-spacing', + 'border-spacing-x', + 'border-spacing-y', 'border-style', 'border-w', 'border-w-b', From c001fc8f7da05b8fe1a76b84e2d925bdf8d07d5a Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Fri, 10 Jun 2022 18:10:44 +0200 Subject: [PATCH 04/13] add grid-flow-dense utility --- src/lib/default-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/default-config.ts b/src/lib/default-config.ts index bac832bc..50ee214d 100644 --- a/src/lib/default-config.ts +++ b/src/lib/default-config.ts @@ -363,7 +363,7 @@ export function getDefaultConfig() { * Grid Auto Flow * @see https://tailwindcss.com/docs/grid-auto-flow */ - 'grid-flow': [{ 'grid-flow': ['row', 'col', 'row-dense', 'col-dense'] }], + 'grid-flow': [{ 'grid-flow': ['row', 'col', 'dense', 'row-dense', 'col-dense'] }], /** * Grid Auto Columns * @see https://tailwindcss.com/docs/grid-auto-columns From 4eda9182d55ce52f3b3b09c71620affda1bc2cf7 Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Fri, 10 Jun 2022 18:16:17 +0200 Subject: [PATCH 05/13] add mix-blend-plus-lighter utility --- src/lib/default-config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/default-config.ts b/src/lib/default-config.ts index 50ee214d..d02a218b 100644 --- a/src/lib/default-config.ts +++ b/src/lib/default-config.ts @@ -75,6 +75,7 @@ export function getDefaultConfig() { 'saturation', 'color', 'luminosity', + 'plus-lighter', ] as const const getAlign = () => ['start', 'end', 'center', 'between', 'around', 'evenly'] as const const getZeroAndEmpty = () => ['', '0', isArbitraryValue] as const From a473214d5ca8b3230ec9fe8b6be86e153325f147 Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 12 Jun 2022 13:23:02 +0200 Subject: [PATCH 06/13] move important modifier in internal modifier string to better position --- src/lib/merge-classlist.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/merge-classlist.ts b/src/lib/merge-classlist.ts index 48c54b26..b89d89fe 100644 --- a/src/lib/merge-classlist.ts +++ b/src/lib/merge-classlist.ts @@ -15,7 +15,7 @@ export function mergeClassList(classList: string, configUtils: ConfigUtils) { * `{importantModifier}{variantModifiers}{classGroupId}` * @example 'float' * @example 'hover:focus:bg-color' - * @example '!md:pr' + * @example 'md:!pr' */ const classGroupsInConflict = new Set() @@ -48,7 +48,7 @@ export function mergeClassList(classList: string, configUtils: ConfigUtils) { : modifiers.sort().concat('').join(MODIFIER_SEPARATOR) const fullModifier = hasImportantModifier - ? IMPORTANT_MODIFIER + variantModifier + ? variantModifier + IMPORTANT_MODIFIER : variantModifier return { @@ -67,7 +67,7 @@ export function mergeClassList(classList: string, configUtils: ConfigUtils) { const { modifier, classGroupId } = parsed - const classId = `${modifier}:${classGroupId}` + const classId = `${modifier}${classGroupId}` if (classGroupsInConflict.has(classId)) { return false @@ -76,7 +76,7 @@ export function mergeClassList(classList: string, configUtils: ConfigUtils) { classGroupsInConflict.add(classId) getConflictingClassGroupIds(classGroupId).forEach((group) => - classGroupsInConflict.add(`${modifier}:${group}`) + classGroupsInConflict.add(`${modifier}${group}`) ) return true From 3c44743b4d2993a8186d8eea025c1a3f2989b680 Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 12 Jun 2022 15:02:17 +0200 Subject: [PATCH 07/13] add tests for arbitrary variants --- tests/arbitrary-variants.test.ts | 74 ++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 tests/arbitrary-variants.test.ts diff --git a/tests/arbitrary-variants.test.ts b/tests/arbitrary-variants.test.ts new file mode 100644 index 00000000..5ab7fab0 --- /dev/null +++ b/tests/arbitrary-variants.test.ts @@ -0,0 +1,74 @@ +import { twMerge } from '../src' + +test('basic arbitrary variants', () => { + expect(twMerge('[&>*]:underline [&>*]:line-through')).toBe('[&>*]:line-through') + expect(twMerge('[&>*]:underline [&>*]:line-through [&_div]:line-through')).toBe( + '[&>*]:line-through [&_div]:line-through' + ) +}) + +test('arbitrary variants with modifiers', () => { + expect(twMerge('dark:lg:hover:[&>*]:underline dark:lg:hover:[&>*]:line-through')).toBe( + 'dark:lg:hover:[&>*]:line-through' + ) + expect(twMerge('dark:lg:hover:[&>*]:underline dark:hover:lg:[&>*]:line-through')).toBe( + 'dark:hover:lg:[&>*]:line-through' + ) + // Whether a modifier is before or after arbitrary variant matters + expect(twMerge('hover:[&>*]:underline [&>*]:hover:line-through')).toBe( + 'hover:[&>*]:underline [&>*]:hover:line-through' + ) + expect( + twMerge( + 'hover:dark:[&>*]:underline dark:hover:[&>*]:underline dark:[&>*]:hover:line-through' + ) + ).toBe('dark:hover:[&>*]:underline dark:[&>*]:hover:line-through') +}) + +test('arbitrary variants with complex syntax in them', () => { + expect( + twMerge( + '[@media_screen{@media(hover:hover)}]:underline [@media_screen{@media(hover:hover)}]:line-through' + ) + ).toBe('[@media_screen{@media(hover:hover)}]:line-through') + expect( + twMerge( + 'hover:[@media_screen{@media(hover:hover)}]:underline hover:[@media_screen{@media(hover:hover)}]:line-through' + ) + ).toBe('hover:[@media_screen{@media(hover:hover)}]:line-through') +}) + +test('arbitrary variants with attribute selectors', () => { + expect(twMerge('[&[data-open]]:underline [&[data-open]]:line-through')).toBe( + '[&[data-open]]:line-through' + ) +}) + +test('arbitrary variants with multiple attribute selectors', () => { + expect( + twMerge( + '[&[data-foo][data-bar]:not([data-baz])]:underline [&[data-foo][data-bar]:not([data-baz])]:line-through' + ) + ).toBe('[&[data-foo][data-bar]:not([data-baz])]:line-through') +}) + +test('multiple arbitrary variants', () => { + expect(twMerge('[&>*]:[&_div]:underline [&>*]:[&_div]:line-through')).toBe( + '[&>*]:[&_div]:line-through' + ) + expect(twMerge('[&>*]:[&_div]:underline [&_div]:[&>*]:line-through')).toBe( + '[&>*]:[&_div]:underline [&_div]:[&>*]:line-through' + ) + expect( + twMerge( + 'hover:dark:[&>*]:focus:disabled:[&_div]:underline dark:hover:[&>*]:disabled:focus:[&_div]:line-through' + ) + ).toBe('dark:hover:[&>*]:disabled:focus:[&_div]:line-through') + expect( + twMerge( + 'hover:dark:[&>*]:focus:[&_div]:disabled:underline dark:hover:[&>*]:disabled:focus:[&_div]:line-through' + ) + ).toBe( + 'hover:dark:[&>*]:focus:[&_div]:disabled:underline dark:hover:[&>*]:disabled:focus:[&_div]:line-through' + ) +}) From 43b9dfa70a30067ef74e9ec3e6aaab34b22ed237 Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 12 Jun 2022 15:39:48 +0200 Subject: [PATCH 08/13] use splitModifiers function instead of regex to support arbitrary variants --- src/lib/merge-classlist.ts | 49 ++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/src/lib/merge-classlist.ts b/src/lib/merge-classlist.ts index b89d89fe..a7789d2e 100644 --- a/src/lib/merge-classlist.ts +++ b/src/lib/merge-classlist.ts @@ -2,9 +2,6 @@ import { ConfigUtils } from './config-utils' const SPLIT_CLASSES_REGEX = /\s+/ const IMPORTANT_MODIFIER = '!' -// Regex is needed, so we don't match against colons in labels for arbitrary values like `text-[color:var(--mystery-var)]` -// I'd prefer to use a negative lookbehind for all supported labels, but lookbehinds don't have good browser support yet. More info: https://caniuse.com/js-regexp-lookbehind -const MODIFIER_SEPARATOR_REGEX = /:(?![^[]*\])/ const MODIFIER_SEPARATOR = ':' export function mergeClassList(classList: string, configUtils: ConfigUtils) { @@ -24,16 +21,10 @@ export function mergeClassList(classList: string, configUtils: ConfigUtils) { .trim() .split(SPLIT_CLASSES_REGEX) .map((originalClassName) => { - const modifiers = originalClassName.split(MODIFIER_SEPARATOR_REGEX) - const classNameWithImportantModifier = modifiers.pop()! + const { modifiers, hasImportantModifier, baseClassName } = + splitModifiers(originalClassName) - const hasImportantModifier = - classNameWithImportantModifier.startsWith(IMPORTANT_MODIFIER) - const className = hasImportantModifier - ? classNameWithImportantModifier.substring(1) - : classNameWithImportantModifier - - const classGroupId = getClassGroupId(className) + const classGroupId = getClassGroupId(baseClassName) if (!classGroupId) { return { @@ -86,3 +77,37 @@ export function mergeClassList(classList: string, configUtils: ConfigUtils) { .join(' ') ) } + +function splitModifiers(className: string) { + const modifiers = [] + + let bracketDepth = 0 + let modifierStart = 0 + + for (const match of className.matchAll(/[:[\]]/g)) { + if (match[0] === ':') { + if (bracketDepth === 0) { + const nextModifierStart = match.index! + 1 + modifiers.push(className.substring(modifierStart, nextModifierStart)) + modifierStart = nextModifierStart + } + } else if (match[0] === '[') { + bracketDepth++ + } else if (match[0] === ']') { + bracketDepth-- + } + } + + const baseClassNameWithImportantModifier = + modifiers.length === 0 ? className : className.substring(modifierStart) + const hasImportantModifier = baseClassNameWithImportantModifier.startsWith(IMPORTANT_MODIFIER) + const baseClassName = hasImportantModifier + ? baseClassNameWithImportantModifier.substring(1) + : baseClassNameWithImportantModifier + + return { + modifiers, + hasImportantModifier, + baseClassName, + } +} From 947aa0497a3c801a6d9a63664ca6b1e0673f4ff5 Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 12 Jun 2022 19:10:37 +0200 Subject: [PATCH 09/13] add support for arbitrary variants in modifier sorting --- src/lib/merge-classlist.ts | 45 +++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/src/lib/merge-classlist.ts b/src/lib/merge-classlist.ts index a7789d2e..665f642e 100644 --- a/src/lib/merge-classlist.ts +++ b/src/lib/merge-classlist.ts @@ -2,7 +2,6 @@ import { ConfigUtils } from './config-utils' const SPLIT_CLASSES_REGEX = /\s+/ const IMPORTANT_MODIFIER = '!' -const MODIFIER_SEPARATOR = ':' export function mergeClassList(classList: string, configUtils: ConfigUtils) { const { getClassGroupId, getConflictingClassGroupIds } = configUtils @@ -33,18 +32,15 @@ export function mergeClassList(classList: string, configUtils: ConfigUtils) { } } - const variantModifier = - modifiers.length === 0 - ? '' - : modifiers.sort().concat('').join(MODIFIER_SEPARATOR) + const variantModifier = sortModifiers(modifiers).join('') - const fullModifier = hasImportantModifier + const modifierId = hasImportantModifier ? variantModifier + IMPORTANT_MODIFIER : variantModifier return { isTailwindClass: true as const, - modifier: fullModifier, + modifierId, classGroupId, originalClassName, } @@ -56,9 +52,9 @@ export function mergeClassList(classList: string, configUtils: ConfigUtils) { return true } - const { modifier, classGroupId } = parsed + const { modifierId, classGroupId } = parsed - const classId = `${modifier}${classGroupId}` + const classId = `${modifierId}${classGroupId}` if (classGroupsInConflict.has(classId)) { return false @@ -67,7 +63,7 @@ export function mergeClassList(classList: string, configUtils: ConfigUtils) { classGroupsInConflict.add(classId) getConflictingClassGroupIds(classGroupId).forEach((group) => - classGroupsInConflict.add(`${modifier}${group}`) + classGroupsInConflict.add(`${modifierId}${group}`) ) return true @@ -111,3 +107,32 @@ function splitModifiers(className: string) { baseClassName, } } + +/** + * Sorts modifiers according to following schema: + * - Predefined modifiers are sorted alphabetically + * - When an arbitrary variant appears, it's important to preserve which modifiers are before and after it + */ +function sortModifiers(modifiers: string[]) { + if (modifiers.length <= 1) { + return modifiers + } + + const sortedModifiers = [] + let unsortedModifiers: string[] = [] + + modifiers.forEach((modifier) => { + const isArbitraryVariant = modifier[0] === '[' + + if (isArbitraryVariant) { + sortedModifiers.push(...unsortedModifiers.sort(), modifier) + unsortedModifiers = [] + } else { + unsortedModifiers.push(modifier) + } + }) + + sortedModifiers.push(...unsortedModifiers.sort()) + + return sortedModifiers +} From 90cdd69a554a5fd5f4b01453e0f616718f37a43e Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 12 Jun 2022 19:12:42 +0200 Subject: [PATCH 10/13] add test for arbitrary variants with arbitrary properties --- tests/arbitrary-variants.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/arbitrary-variants.test.ts b/tests/arbitrary-variants.test.ts index 5ab7fab0..7743825b 100644 --- a/tests/arbitrary-variants.test.ts +++ b/tests/arbitrary-variants.test.ts @@ -72,3 +72,12 @@ test('multiple arbitrary variants', () => { 'hover:dark:[&>*]:focus:[&_div]:disabled:underline dark:hover:[&>*]:disabled:focus:[&_div]:line-through' ) }) + +test('arbitrary variants with arbitrary properties', () => { + expect(twMerge('[&>*]:[color:red] [&>*]:[color:blue]')).toBe('[&>*]:[color:blue]') + expect( + twMerge( + '[&[data-foo][data-bar]:not([data-baz])]:nod:noa:[color:red] [&[data-foo][data-bar]:not([data-baz])]:noa:nod:[color:blue]' + ) + ).toBe('[&[data-foo][data-bar]:not([data-baz])]:noa:nod:[color:blue]') +}) From 5fca7546b946bf400c7f5e1230801072c6b29cab Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 12 Jun 2022 19:50:57 +0200 Subject: [PATCH 11/13] add arbitrary variants to docs --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 95a69d8e..e952a2da 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,16 @@ twMerge('[padding:1rem] p-8') // → '[padding:1rem] p-8' Watch out for mixing arbitrary properties which could be expressed as Tailwind classes. tailwind-merge does not resolve conflicts between arbitrary properties and their matching Tailwind classes to keep the bundle size small. +### Supports arbitrary variants + +```ts +twMerge('[&:nth-child(3)]:py-0 [&:nth-child(3)]:py-4') // → '[&:nth-child(3)]:py-4' +twMerge('dark:hover:[&:nth-child(3)]:py-0 hover:dark:[&:nth-child(3)]:py-4') +// → 'hover:dark:[&:nth-child(3)]:py-4' +``` + +Similarly to arbitrary properties, tailwind-merge does not resolve conflicts between arbitrary variants and their matching predefined modifiers, like `[&:focus]:ring` and `focus:ring`. + ### Supports important modifier ```ts From 772c997248089ae92e464a6846baa371017e872e Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 12 Jun 2022 19:55:49 +0200 Subject: [PATCH 12/13] add more clarity to docs --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e952a2da..d73294b8 100644 --- a/README.md +++ b/README.md @@ -120,9 +120,12 @@ Watch out for mixing arbitrary properties which could be expressed as Tailwind c twMerge('[&:nth-child(3)]:py-0 [&:nth-child(3)]:py-4') // → '[&:nth-child(3)]:py-4' twMerge('dark:hover:[&:nth-child(3)]:py-0 hover:dark:[&:nth-child(3)]:py-4') // → 'hover:dark:[&:nth-child(3)]:py-4' + +// Don't do this! +twMerge('[&:focus]:ring focus:ring-4') // → '[&:focus]:ring focus:ring-4' ``` -Similarly to arbitrary properties, tailwind-merge does not resolve conflicts between arbitrary variants and their matching predefined modifiers, like `[&:focus]:ring` and `focus:ring`. +Similarly to arbitrary properties, tailwind-merge does not resolve conflicts between arbitrary variants and their matching predefined modifiers for bundle size reasons. ### Supports important modifier From 7bf25cfa2bf295c6ad428928e86037d35220cebf Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 12 Jun 2022 19:55:55 +0200 Subject: [PATCH 13/13] fix README test --- tests/readme-examples.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/readme-examples.test.ts b/tests/readme-examples.test.ts index 8f739060..65d942a2 100644 --- a/tests/readme-examples.test.ts +++ b/tests/readme-examples.test.ts @@ -3,10 +3,10 @@ import fs from 'fs' import { twMerge } from '../src' const twMergeExampleRegex = - /twMerge\((?[\w\s\-:[\]#(),!\n'"]+?)\)(?!.*(?.+)['"]/g + /twMerge\((?[\w\s\-:[\]#(),!&\n'"]+?)\)(?!.*(?.+)['"]/g test('readme examples', () => { - expect.assertions(21) + expect.assertions(24) return fs.promises .readFile(`${__dirname}/../README.md`, { encoding: 'utf-8' })