From 046b98f9a774903f3720596781c7bf4362397f16 Mon Sep 17 00:00:00 2001 From: Ulysse ARNAUD Date: Sun, 28 May 2023 20:24:43 +0200 Subject: [PATCH] 0.2 WIP This is a major version : anterior versions to 0.2 used an old mechanism to retrieve group of styles in class attributes. Nowadays, regular expressions are used instead. --- .github/workflows/deploy-image.yml | 89 ------ .gitignore | 2 - README.md | 38 +-- jest-puppeteer.config.js | 6 - jest.config.js | 4 - main.js | 463 +++++++++++++++++++++++++++ package.json | 34 +- src/index.js | 490 ----------------------------- src/polyfill.js | 203 ------------ tests/classes.test.js | 23 -- tests/events.test.js | 15 - tests/index.html | 19 -- tests/media-queries.test.js | 15 - tests/selectors.test.js | 87 ----- tests/utils.js | 77 ----- 15 files changed, 473 insertions(+), 1092 deletions(-) delete mode 100644 .github/workflows/deploy-image.yml delete mode 100644 jest-puppeteer.config.js delete mode 100644 jest.config.js create mode 100644 main.js delete mode 100644 src/index.js delete mode 100644 src/polyfill.js delete mode 100644 tests/classes.test.js delete mode 100644 tests/events.test.js delete mode 100644 tests/index.html delete mode 100644 tests/media-queries.test.js delete mode 100644 tests/selectors.test.js delete mode 100644 tests/utils.js diff --git a/.github/workflows/deploy-image.yml b/.github/workflows/deploy-image.yml deleted file mode 100644 index 1256e7d..0000000 --- a/.github/workflows/deploy-image.yml +++ /dev/null @@ -1,89 +0,0 @@ -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: get-npm-version - id: package-version - uses: martinbeentjes/npm-get-version-action@main - - - name: create-new-branch - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: "New release: ${{ steps.package-version.outputs.current-version }}" - branch: ${{ steps.package-version.outputs.current-version }} - repository: . - - - uses: actions/setup-node@v3 - with: - node-version: 16 - - run: npm install - - run: npm run format:all - - run: npm run lint:fix:all - - run: npm run build:all - - # - run: git config advice.addIgnoredFile false - - - run: rm .gitignore - - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_options: '--no-verify' - commit_user_name: github-actions - commit_user_email: noreply@arnaud.tech - commit_author: github-actions - commit_message: "Remove .gitignore" - skip_dirty_check: true - push_options: '--force' - create_branch: true - branch: ${{ steps.package-version.outputs.current-version }} - file_pattern: ./.gitignore - - - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Updated build files - commit_options: '--no-verify' - commit_user_name: github-actions - commit_user_email: noreply@arnaud.tech - commit_author: github-actions - file_pattern: - build/index.min.js - build/polyfill.min.js - package-lock.json - skip_dirty_check: true - push_options: '--force' - branch: ${{ steps.package-version.outputs.current-version }} - create_branch: true - - publish-github-release: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: get-npm-version - id: package-version - uses: martinbeentjes/npm-get-version-action@main - - uses: actions/checkout@v3 - with: - ref: ${{ steps.package-version.outputs.current-version }} - - name: create-new-release - uses: softprops/action-gh-release@v1 - with: - files: | - build/index.min.js - # build/index.min.js.map - build/polyfill.min.js - # build/polyfill.min.js.map - package-lock.json - tag_name: ${{ steps.package-version.outputs.current-version }} - name: ${{ steps.package-version.outputs.current-version }} - body: | - Release ${{ steps.package-version.outputs.current-version }} - draft: false - prerelease: false - NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5cfd34c..7a3d9c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ node_modules -index.min.js -*.map pacakge-lock.json \ No newline at end of file diff --git a/README.md b/README.md index ff72d74..5a9845d 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,8 @@ CSS-in-JS-in-HTML is a JavaScript library which permits you to apply CSS propert ### Prerequisites +> ⚠️ The following prerequisites were tested with the version 0.1.6 of the library and not the current version. However, the compatibility should be the same. + The followed prerequisites are the minimum requirements to use the library. The library may work with older browsers with the polyfills but it's not guaranteed (and recommended). __Modern browers__ @@ -107,9 +109,9 @@ __Older browsers__ #### From CDN -1. Import `index.min.js` (or `index.js`) in your HTML file +1. Import `main.js` in your HTML file ```html - + ``` 2. Add one line of CSS to hide the page while building (and permits to hide elements with hidden attribute) @@ -117,43 +119,17 @@ __Older browsers__ html[aria-busy="true"], [hidden] { display: none!important; } ``` -3. Call `CSS_IN_JS_IN_HTML.init(document,null)` to start the library. The optimal use case is to call it in the `DOMContentLoaded` event. - ```js - document.addEventListener('DOMContentLoaded', () => CSS_IN_JS_IN_HTML.init(document, null)); - ``` - #### Manual installation 1. Clone the repo ```sh git clone https://github.com/ulyssear/css-in-js-in-html.git ``` -2. Import `index.min.js` (or `index.js`) in your HTML file +2. Import `main.js` in your HTML file ```html - + ``` -#### (Optional) Add polyfills - -Some polyfills are required to make the library work on older browsers (IE9 and above). You can add them by importing the following file in your HTML file. - -```html - -``` - -The polyfills permit to use the following features : -- `window.getComputedStyle` -- `Array.isArray` -- `Array.prototype.lastIndexOf` -- `Array.prototype.reduce` -- `addEventListener` -- `window.matchMedia` -- `document.querySelectorAll` -- `document.querySelector` -- `String.prototype.trim` - -It's recommended to not use the polyfills if you don't need to support old browsers. -

(back to top)

@@ -180,7 +156,6 @@ See the [open issues](https://github.com/ulyssear/css-in-js-in-html/issues) for

(back to top)

- ## Contributing @@ -234,7 +209,6 @@ Project Link: [https://github.com/ulyssear/css-in-js-in-html](https://github.com [license-url]: https://github.com/ulyssear/css-in-js-in-html/blob/master/LICENSE [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 [linkedin-url]: https://linkedin.com/in/ulyssearnaud -[product-screenshot]: images/screenshot.png [JavaScript-shield]: https://img.shields.io/badge/JavaScript-323330?style=for-the-badge&logo=javascript&logoColor=F7DF1E [JavaScript-url]: https://www.javascript.com/ [NPM-shield]: https://img.shields.io/badge/npm-CB3837?style=for-the-badge&logo=npm&logoColor=white diff --git a/jest-puppeteer.config.js b/jest-puppeteer.config.js deleted file mode 100644 index 0f52f5d..0000000 --- a/jest-puppeteer.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - "launch": { - "headless": "true" - }, - "browserContext": "default" -} \ No newline at end of file diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index e18070b..0000000 --- a/jest.config.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - "preset": "jest-puppeteer", - "verbose": true -} diff --git a/main.js b/main.js new file mode 100644 index 0000000..4fde0f5 --- /dev/null +++ b/main.js @@ -0,0 +1,463 @@ +const regexes = { + events: /(\[[\w\-,]+\])/, + selectors: /((?~+'"=*:,\(\)]+\])/, + "group-classes": /(\{[\n\r\s\w\d\-\[\]\/\#\(\)\{\},\.\*\'\"%=\+]+\})/, + classes: + /((?:[\w\d\-]+-\[[\/\'\"\n\r\s\w\d\#\(\)\{\}\.\-,%=\+]+\])(?! *[a-zA-Z0-9]+\[))/, + media: /\[@media\((.*)\)\]/, +}; + +const regexes_2 = { + "media,events,selectors,group-classes": + /(\[@media\((.*)\)\]):(\[[\w\-,]+\]):((?~+'"=*:,\(\)]+\]):(\{[\n\r\s\w\d\-\[\]\/\#\(\)\{\},\.\*\'\"%=\+]+\})/gi, + "media,events,selectors,classes": + /(\[@media\((.*)\)\]):(\[[\w\-,]+\]):((?~+'"=*:,\(\)]+\]):((?:[\w\d\-]+-\[[\/\'\"\n\r\s\w\d\#\(\)\{\}\.\-,%=\+]+\])(?! *[a-zA-Z0-9]+\[))/gi, + "media,events,group-classes": + /(\[@media\((.*)\)\]):(\[[\w\-,]+\]):(\{[\n\r\s\w\d\-\[\]\/\#\(\)\{\},\.\*\'\"%=\+]+\})/gi, + "media,events,classes": + /(\[@media\((.*)\)\]):((?:[\w\d\-]+-\[[\/\'\"\n\r\s\w\d\#\(\)\{\}\.\-,%=\+]+\])(?! *[a-zA-Z0-9]+\[))/gi, + "media,selectors,group-classes": + /(\[@media\((.*)\)\]):((?~+'"=*:,\(\)]+\]):(\{[\n\r\s\w\d\-\[\]\/\#\(\)\{\},\.\*\'\"%=\+]+\})/gi, + "media,selectors,classes": + /(\[@media\((.*)\)\]):((?~+'"=*:,\(\)]+\]):((?:[\w\d\-]+-\[[\/\'\"\n\r\s\w\d\#\(\)\{\}\.\-,%=\+]+\])(?! *[a-zA-Z0-9]+\[))/gi, + "media,group-classes": + /(\[@media\((.*)\)\]):(\{[\n\r\s\w\d\-\[\]\/\#\(\)\{\},\.\*\'\"%=\+]+\})/gi, + "media,classes": + /(\[@media\((.*)\)\]):((?:[\w\d\-]+-\[[\/\'\"\n\r\s\w\d\#\(\)\{\}\.\-,%=\+]+\])(?! *[a-zA-Z0-9]+\[))/gi, + + "events,selectors,group-classes": + /(\[[\w\-,]+\]):((?~+'"=*:,\(\)]+\]):(\{[\n\r\s\w\d\-\[\]\/\#\(\)\{\},\.\*\'\"%=\+]+\})/gi, + "events,selectors,classes": + /(\[[\w\-,]+\]):((?~+'"=*:,\(\)]+\]):((?:[\w\d\-]+-\[[\/\'\"\n\r\s\w\d\#\(\)\{\}\.\-,%=\+]+\])(?! *[a-zA-Z0-9]+\[))/gi, + "events,group-classes": + /(\[[\w\-,]+\]):(\{[\n\r\s\w\d\-\[\]\/\#\(\)\{\},\.\*\'\"%=\+]+\})/gi, + "events,classes": + /(\[[\w\-,]+\]):((?:[\w\d\-]+-\[[\/\'\"\n\r\s\w\d\#\(\)\{\}\.\-,%=\+]+\])(?! *[a-zA-Z0-9]+\[))/gi, + "selectors,group-classes": + /((?~+'"=*:,\(\)]+\]):(\{[\n\r\s\w\d\-\[\]\/\#\(\)\{\},\.\*\'\"%=\+]+\})/gi, + "selectors,classes": + /((?~+'"=*:,\(\)]+\]):((?:[\w\d\-]+-\[[\/\'\"\n\r\s\w\d\#\(\)\{\}\.\-,%=\+]+\])(?! *[a-zA-Z0-9]+\[))/gi, + "group-classes": /(\{[\n\r\s\w\d\-\[\]\/\#\(\)\{\},\.\*\'\"%=\+]+\})/gi, + classes: + /((?:[\w\d\-]+-\[[\/\'\"\n\r\s\w\d\#\(\)\{\}\.\-,%=\+]+\])(?! *[a-zA-Z0-9]+\[))/gi, +}; + +const regexes_comments = { + inline: /\/\/[^\n\r]*/g, + block: /\/\*[\n\r\s\w\d\-\[\]\/\#\(\)\{\},\'\"%= ]+\*\//g, +}; + +function retrieve_regex(name) { + if (regexes[name] === undefined) { + throw new Error(`Regex ${name} not found`); + } + + return regexes[name].source; +} + +function retrieve_regexes(names) { + return ( + regexes_2[names] || + new RegExp(names.split(",").map(retrieve_regex).join(":"), "gi") + ); +} + +function retrieve_selector(element) { + const tag = element.tagName.toLowerCase(); + const id = element.id ? `#${element.id}` : ""; + const nth_child = element.parentNode + ? `:nth-child(${ + Array.from(element.parentNode.children).indexOf(element) + 1 + })` + : ""; + return `${tag}${id}${nth_child}`; +} + +function retrieve_groups(_class) { + const groups = []; + + const regexes_to_check = [ + "events,selectors,group-classes", + "events,selectors,classes", + "events,group-classes", + "events,classes", + "selectors,group-classes", + "selectors,classes", + "group-classes", + "classes", + ]; + + _class = _class.replace(/[\n\t]/g, " "); + + for (let i = 0; i < regexes_to_check.length; i++) { + const regexes = retrieve_regexes(regexes_to_check[i]); + + if (!regexes.test(_class)) continue; + + const number_parts = _class.match(regexes); + + if (!number_parts) continue; + + for (let j = 0; j < number_parts.length; j++) { + const current_number_parts = number_parts[j].trim(); + + const current_groups = current_number_parts + .split(regexes) + .filter(Boolean); + + try { + if ( + "[" === current_groups[0][0] && + "]" === current_groups[1][current_groups[1].length - 1] && + "[" !== current_groups[1][0] + ) { + current_groups[0] += current_groups[1]; + current_groups.splice(1, 1); + } + } catch (e) {} + + const entry = {}; + + const keys = regexes_to_check[i].split(","); + for (let k = 0; k < keys.length; k++) { + const key = keys[k]; + entry[key] = current_groups[k]; + } + + if (entry.media) { + } + + let not_valid = true; + + if (entry.events) { + const events = (function (events) { + if (events[0] === "[" && events[events.length - 1] === "]") { + events = events.slice(1, -1); + } + return events.split(","); + })(entry.events); + for (let k = 0; k < events.length; k++) { + const event = events[k]; + if (check_if_match_event(event)) { + not_valid = false; + break; + } + } + } + + if (entry.selectors || (entry.events && not_valid)) { + if (entry.events && not_valid) { + entry.selectors = entry.events; + delete entry.events; + } + entry.selectors = (function (selectors) { + if (selectors[0] === "[" && selectors[selectors.length - 1] === "]") { + selectors = selectors.slice(1, -1); + } + const regex_split = /,(?![^\[\()]*[\]\)])/g; + return selectors.split(regex_split); + })(entry.selectors); + } + + if (entry["group-classes"]) { + entry.styles = retrieve_styles(entry["group-classes"]); + delete entry["group-classes"]; + } + + if (entry.classes) { + entry.styles = { ...entry.styles, ...retrieve_styles(entry.classes) }; + delete entry.classes; + } + + if ( + Object.keys(entry) + .filter((key) => key !== "source") + .every((key) => !entry[key]) + ) { + continue; + } + + _class = _class.replace(current_number_parts, ""); + entry.source = current_number_parts; + + groups.push(entry); + } + } + + for (let i = 0; i < groups.length; i++) { + const group = groups[i]; + for (let j = i + 1; j < groups.length; j++) { + const group2 = groups[j]; + if ( + group.selectors === group2.selectors && + group.events === group2.events + ) { + group.styles = { ...group.styles, ...group2.styles }; + group.source += " " + group2.source; + groups.splice(j, 1); + j--; + } + } + } + + return groups; +} + +function retrieve_styles(classes) { + const styles = {}; + + if (Array.isArray(classes)) classes = classes.join(" "); + + classes = classes.replace(/\{|\}/g, "").trim(); + + classes = classes.split(/(? + k.startsWith("on") && + (document[k] == null || typeof document[k] == "function") + ) + ), +].map((k) => k.substring(2)); + +function check_if_match_event(name) { + return name.split(",").some((event) => events.includes(event)); +} + +function apply() { + const elements = Array.from( + document.querySelectorAll(":not(template)[class]") + ); + + if (document.documentElement.getAttribute("class")) { + elements.push(document.documentElement); + } + + let cursor_class; + + for (let i = 0; i < elements.length; i++) { + cursor_class = elements[i].getAttribute("class"); + + if (!cursor_class) { + elements[i].removeAttribute("class"); + continue; + } + + cursor_class = cursor_class.replace(/[\n\t]/g, " ").trim(); + cursor_class = cursor_class.replace(/\s+/g, " "); + + for (let regex_comment in regexes_comments) { + cursor_class = cursor_class.replace(regexes_comments[regex_comment], ""); + } + + const groups = retrieve_groups(cursor_class); + + for (let j = 0; j < groups.length; j++) { + const { media, styles, selectors, events, source } = { + media: "", + styles: {}, + selectors: [], + events: [], + source: "", + ...groups[j], + }; + + cursor_class = cursor_class.replace(source, "").trim(); + + if (media.length) { + } + + if (events.length) { + for (let s = 0; s < selectors.length; s++) { + append_event({ + element: elements[i], + selector: selectors[s], + event: events, + styles, + media, + }); + } + } + + if (selectors.length) { + if (Object.keys(styles).length) { + for (let k = 0; k < selectors.length; k++) { + const elements_from_selector = get_elements_from_selector( + elements[i], + selectors[k] + ); + for (let l = 0; l < elements_from_selector.length; l++) { + for (let property in styles) { + elements_from_selector[l].style.setProperty( + property, + styles[property] + ); + } + } + } + } + + continue; + } else if (Object.keys(styles).length) { + for (let property in styles) { + elements[i].style.setProperty(property, styles[property]); + } + continue; + } + } + + if (cursor_class) elements[i].className = cursor_class; + else elements[i].removeAttribute("class"); + } +} + +function get_elements_from_selector(element, selector) { + const elements = []; + if (">" === selector[0]) { + selector = get_element_selector(element) + selector; + elements.push(...element.parentNode.querySelectorAll(selector)); + } else { + if ("current" === selector) { + elements.push(element); + } else { + elements.push(...element.querySelectorAll(selector)); + } + } + return elements; +} + +function append_event({ element, selector, media, event, styles }) { + if (event[0] === "[") { + event = event.replace("[", "").replace("]", "").split(","); + } + if (!element.style_events) { + element.style_events = {}; + } + if (!element.style_events[selector]) element.style_events[selector] = {}; + + for (let i = 0; i < event.length; i++) { + const elements_from_selector = get_elements_from_selector( + element, + selector + ); + for (let j = 0; j < elements_from_selector.length; j++) { + if (!elements_from_selector[j].style_events) { + elements_from_selector[j].style_events = {}; + } + if (!elements_from_selector[j].style_events[event[i]]) + elements_from_selector[j].style_events[event[i]] = {}; + if (!elements_from_selector[j].style_events[event[i]][media]) + elements_from_selector[j].style_events[event[i]][media] = {}; + for (let property in styles) { + elements_from_selector[j].style_events[event[i]][media][property] = + styles[property]; + } + elements_from_selector[j].addEventListener(event[i], (event) => + _listener(event) + ); + } + } + + function _listener(event) { + const { target } = event; + if (!target.style_events) return; + const { style_events } = target; + const styles = style_events[event.type]; + for (let media in styles) { + if (media === "null" || media === "") { + _apply_styles(target, styles[media]); + } else { + const media_query = window.matchMedia(media); + if (media_query.matches) { + _apply_styles(target, styles[media]); + } + } + } + } + + function _apply_styles(element, styles) { + for (let property in styles) { + element.style.setProperty(property, styles[property]); + } + } +} + +function get_element_selector(element) { + const { id, tagName } = element; + if (["HTML", "BODY"].includes(tagName)) return tagName; + if (id) return `#${id}`; + else { + const parent = element.parentElement; + const children = parent.children; + const index = Array.prototype.indexOf.call(children, element); + return `${get_element_selector(parent)} > ${tagName}:nth-child(${ + index + 1 + })`; + } +} + +function run() { + apply(); + + const body = document.documentElement; + const observer = new MutationObserver((mutations) => { + for (let i = 0; i < mutations.length; i++) { + const mutation = mutations[i]; + switch (mutation.type) { + case "attributes": + if ("class" === mutation.attributeName) { + apply(); + } + break; + case "childList": + if (mutation.addedNodes.length) { + apply(); + } + break; + default: + break; + } + } + }); + observer.observe(body, { + childList: true, + subtree: true, + attributes: true, + }); + + const fonts = document.fonts; + if (fonts) { + const fonts_loaded = fonts.ready; + if (fonts_loaded) { + fonts_loaded.then(disable_busy); + } + } else { + disable_busy(); + } + + function disable_busy() { + document.documentElement.setAttribute("aria-busy", "false"); + } +} + +document.addEventListener("DOMContentLoaded", () => run()); diff --git a/package.json b/package.json index 497bb14..a7b31eb 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,10 @@ { "name": "@ulyssear/css-in-js-in-html", - "version": "0.1.6", + "version": "0.2.0", "description": "CSS-in-JS-in-HTML is a library that allows you to use CSS-in-JS with HTML only.", - "main": "build/index.min.js", + "main": "main.js", "scripts": { - "test": "jest", - "format": "rome format --write --indent-style tab --line-width 160 --quote-style single src/index.js", - "format:polyfill": "rome format --write --indent-style tab --line-width 160 --quote-style single src/polyfill.js", - "format:all": "rome format --write --indent-style tab --line-width 160 --quote-style single src", - "lint": "rome check src/index.js", - "lint:polyfill": "rome check src/polyfill.js", - "lint:fix": "rome check --apply-suggested src/index.js", - "lint:fix:polyfill": "rome check --apply-suggested src/polyfill.js", - "lint:all": "rome check src", - "lint:fix:all": "rome check --apply-suggested src", - "build": "esbuild src/index.js --bundle --outfile=build/index.min.js --minify", - "build:polyfill": "esbuild src/polyfill.js --bundle --outfile=build/polyfill.min.js --minify", - "build:all": "npm run build && npm run build:polyfill", - "build:hot": "npm run build -- --watch", - "build:hot:polyfill": "npm run build:polyfill -- --watch", - "start": "concurrently -n \"build,build:polyfill,serve\" \"npm run build:hot\" \"npm run build:hot:polyfill\" \"npx serve build\"" + "tests": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", @@ -30,16 +15,5 @@ "url": "https://github.com/ulyssear/css-in-js-in-html/issues" }, "homepage": "https://github.com/ulyssear/css-in-js-in-html#readme", - "license": "MIT", - "dependencies": { - "esbuild": "^0.16.13", - "rome": "^11.0.0", - "serve": "^14.1.2" - }, - "devDependencies": { - "concurrently": "^7.6.0", - "jest": "^29.3.1", - "jest-puppeteer": "^6.2.0", - "puppeteer": "^19.5.0" - } + "license": "MIT" } diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 90d5e33..0000000 --- a/src/index.js +++ /dev/null @@ -1,490 +0,0 @@ -const generic_opened_characters = { - parenthesis: 0, - bracket: 0, - brace: 0, - single_quote: 0, - double_quote: 0, - backtick: 0, -}; - -const dict_characters = { - '(': 'parenthesis-start', - ')': 'parenthesis-end', - '[': 'bracket-start', - ']': 'bracket-end', - '{': 'brace-start', - '}': 'brace-end', - "'": 'single_quote', - '"': 'double_quote', - '`': 'backtick', -}; - -const generic_entry = { - original: undefined, - events: undefined, - selectors: undefined, - classes: undefined, -}; - -function split_classname_to_classes_groups(className) { - const opened_characters = Object.assign({}, generic_opened_characters); - - const group_classes = []; - let temp_class = ''; - - for (let i = 0; i < className.length; i++) { - let character = className[i]; - const next_character = className[i + 1]; - - if (character + next_character === '//') { - i += className.slice(i).indexOf('\n'); - continue; - } - - if (character + next_character === '/*') { - i += className.slice(i).indexOf('*/') + 1; - continue; - } - - const character_type = { - type: dict_characters[character]?.replace('-start', '').replace('-end', ''), - variant: dict_characters[character] ? dict_characters[character].split('-')[1] : undefined, - }; - - if (character_type.variant === 'start') { - opened_characters[character_type.type] += 1; - } - - if (character_type.variant === 'end') { - opened_characters[character_type.type] -= 1; - } - - const total_opened_characters = Object.values(opened_characters).reduce((a, b) => a + b, 0); - - if (['\t', '\r', '\n'].includes(character)) { - character = ' '; - } - - temp_class += character; - - if (0 === total_opened_characters && 0 < temp_class.length && (i === className.length - 1 || ' ' === character)) { - const entry = Object.assign({}, generic_entry); - entry.original = temp_class.trim(); - - const opened_characters_entry = Object.assign({}, generic_opened_characters); - - const group_classes_entry = []; - let temp_group = ''; - - for (let j = 0; j < entry.original.length; j++) { - const character = entry.original[j]; - - const character_type = { - type: dict_characters[character]?.replace('-start', '').replace('-end', ''), - variant: dict_characters[character] ? dict_characters[character].split('-')[1] : undefined, - }; - - if (character_type.variant === 'start') { - opened_characters_entry[character_type.type] += 1; - } - - if (character_type.variant === 'end') { - opened_characters_entry[character_type.type] -= 1; - } - - const total_opened_characters_entry = Object.values(opened_characters_entry).reduce((a, b) => a + b, 0); - - temp_group += character; - - if (0 === total_opened_characters_entry && (':' === character || j === entry.original.length - 1)) { - group_classes_entry.push(temp_group.trim()); - - if (0 < group_classes_entry.length && temp_group.trim().startsWith('[@media(') && temp_group.trim().endsWith(']:')) { - entry.media_query = temp_group.trim().substring(8, temp_group.trim().length - 3); - } - - temp_group = ''; - } - } - - if (1 > group_classes_entry.length) { - continue; - } - - // from serialized classes to array of classes - entry.classes = (function deserializeClasses(_classes) { - if (_classes.startsWith('{') && _classes.endsWith('}')) { - _classes = _classes.substring(1, _classes.length - 1); - } - const opened_characters_classes = Object.assign({}, generic_opened_characters); - const classes = []; - let temp_class_entry = ''; - for (let k = 0; k < _classes.length; k++) { - const character = _classes[k]; - const character_type = { - type: dict_characters[character]?.replace('-start', '').replace('-end', ''), - variant: dict_characters[character] ? dict_characters[character].split('-')[1] : undefined, - }; - - if (character_type.variant === 'start') { - opened_characters_classes[character_type.type] += 1; - } - - if (character_type.variant === 'end') { - opened_characters_classes[character_type.type] -= 1; - } - - const total_opened_characters_classes = Object.values(opened_characters_classes).reduce((a, b) => a + b, 0); - - temp_class_entry += character; - - if (0 === total_opened_characters_classes && (character === ' ' || k === _classes.length - 1) && '' !== temp_class_entry.trim()) { - classes.push(temp_class_entry.trim()); - temp_class_entry = ''; - } - } - // console.log({classes}) - return classes; - })(group_classes_entry[group_classes_entry.length - 1]); - - // from serialized events to array of events - entry.events = (function deserializeEvents(_events) { - if (!_events) return undefined; - if (_events.startsWith('[') || _events.endsWith(']')) return undefined; - if (_events.endsWith(':')) { - _events = _events.substring(0, _events.length - 1); - } - if (_events.startsWith('{') && _events.endsWith('}')) { - _events = _events.substring(1, _events.length - 1); - } - return _events.split(','); - })(!entry.media_query ? group_classes_entry[group_classes_entry.length - 3] : undefined); - - // from serialized selectors to array of selectors - entry.selectors = (function deserializeSelectors(_selectors) { - if (!_selectors) return undefined; - if (!(_selectors.startsWith('[') && (_selectors.endsWith(']') || _selectors.endsWith(']:')))) return undefined; - if (_selectors.startsWith('[@media(')) return undefined; - - _selectors = _selectors.substring(1, _selectors.length - (1 + +_selectors.endsWith(']:'))); - - if (_selectors.includes('@lookout')) { - const [selector_to_look, selector_to_apply] = _selectors.split('@lookout'); - return { - tag: 'lookout', - before: deserializeSelectors(selector_to_look.trim()), - after: deserializeSelectors(selector_to_apply.trim()), - }; - } - - if (['>', '+', '~'].includes(_selectors[0])) { - return { - tag: _selectors[0], - selectors: deserializeSelectors(`[${_selectors.substring(1).trim()}]`), - }; - } - - const opened_characters_selectors = Object.assign({}, generic_opened_characters); - const selectors = []; - let temp_selector_entry = ''; - - for (let k = 0; k < _selectors.length; k++) { - const character = _selectors[k]; - const character_type = { - type: dict_characters[character]?.replace('-start', '').replace('-end', ''), - variant: dict_characters[character] ? dict_characters[character].split('-')[1] : undefined, - }; - - if (character_type.variant === 'start') { - opened_characters_selectors[character_type.type] += 1; - } - - if (character_type.variant === 'end') { - opened_characters_selectors[character_type.type] -= 1; - } - - const total_opened_characters_selectors = Object.values(opened_characters_selectors).reduce((a, b) => a + b, 0); - - temp_selector_entry += character; - - // console.log(temp_selector_entry) - - if (0 === total_opened_characters_selectors && (character === ',' || k === _selectors.length - 1)) { - let selector = temp_selector_entry.trim(); - if (selector.startsWith(',')) { - selector = selector.substring(1, selector.length); - } - if (selector.endsWith(',')) { - selector = selector.substring(0, selector.length - 1); - } - selectors.push(selector); - temp_selector_entry = ''; - } - } - return selectors; - })(!entry.media_query ? group_classes_entry[group_classes_entry.length - 2] : group_classes_entry[group_classes_entry.length - 3]); - - temp_class = ''; - group_classes.push(entry); - // console.log({entry}) - } - } - - return group_classes; -} - -function apply_custom_class(element, className) { - // window.addEventListener('resize', () => apply_custom_class(element, className)); - - const group_classes = split_classname_to_classes_groups(className); - // console.log({group_classes}) - - for (let i = 0; i < group_classes.length; i++) { - const { selectors, classes, events, media_query } = group_classes[i]; - - let classes_to_apply = []; - for (let j = 0; j < classes.length; j++) { - const class_entry = classes[j]; - - if ('[' === class_entry[0] && ']' !== class_entry[1]) { - apply_custom_class(element, class_entry); - continue; - } - - const lastIndexOf = class_entry.lastIndexOf('-['); - const class_name = class_entry.substring(0, lastIndexOf); - const class_value = class_entry.substring(lastIndexOf + 1, class_entry.length); - classes_to_apply.push({ - name: class_name, - value: class_value.substring(1, class_value.length - 1), - }); - } - const original_class = className; - - // console.log({ selectors, classes_to_apply, events, media_query }); - - do_apply(element, selectors, classes_to_apply, events, media_query, original_class); - } -} - -function do_apply(element, selectors, classes, events, media_query, original_class) { - // console.log({element,selectors,classes,original_class}) - if (media_query) { - if ('(' !== media_query[0]) { - media_query = `(${media_query})`; - } - if (!window.matchMedia(media_query).matches) { - return; - } - } - - let elements_to_apply = [element]; - - if (selectors) { - elements_to_apply = []; - if ('object' === typeof selectors && !Array.isArray(selectors)) { - selectors = [selectors]; - } - for (let j = 0; j < selectors.length; j++) { - let selector = selectors[j]; - if ('object' === typeof selector) { - const { tag, selectors: _selectors } = selector; - if ('lookout' === tag) { - let before_to_apply = []; - let after_to_apply = []; - const { before, after } = selector; - // checks if before selector matches - for (let b = 0; b < before.length; b++) { - const before_selector = before[b]; - const elements = document.querySelectorAll(before_selector); - for (let e = 0; e < elements.length; e++) { - const element = elements[e]; - if (element.matches(before_selector)) { - elements_to_apply.push(element); - before_to_apply.push(element); - } - } - } - // checks if after selector matches - for (let a = 0; a < after.length; a++) { - const after_selector = after[a]; - const elements = document.querySelectorAll(after_selector); - for (let e = 0; e < elements.length; e++) { - const element = elements[e]; - if (element.matches(after_selector)) { - elements_to_apply.push(element); - after_to_apply.push(element); - } - } - } - } - // For each children within children of element matching selector - if ('>' === tag && _selectors) { - do_apply(element, [`@current>${_selectors}`], classes, events, media_query); - } - // - // Next sibling of element matching selector - if ('+' === tag && _selectors) { - const { selectors } = selector; - const { nextElementSibling } = element; - if (!nextElementSibling) continue; - if (!nextElementSibling.matches(selectors)) continue; - do_apply(nextElementSibling, null, classes, events, media_query); - } - continue; - } - if (/\@current/.test(selector)) { - selector = selector.replace(/\@current/g, retrieve_current_selector(element)); - } - const elements = 'HTML' !== selector ? element.querySelectorAll(selector) : [document.documentElement]; - for (let k = 0; k < elements.length; k++) { - elements_to_apply.push(elements[k]); - } - } - element.className = element.className.replace("\r", '').replace("\n", '').replace("\t", '').replace(/\s\s+/g, ' ').trim(); - element.className = element.className.replace(/\[.+\]:\{(?:\s+)?\}/g, '').trim(); - } - - if (events) { - for (let j = 0; j < events.length; j++) { - const event = events[j]; - for (let k = 0; k < elements_to_apply.length; k++) { - const element_to_apply = elements_to_apply[k]; - element_to_apply.addEventListener( - event, - () => { - for (let l = 0; l < classes.length; l++) { - const { name, value } = classes[l]; - element.style.setProperty(name, value); - } - }, - false, - ); - } - } - return; - } - - let to_remove_class = false; - - for (let j = 0; j < elements_to_apply.length; j++) { - const element_to_apply = elements_to_apply[j]; - if (!element_to_apply) continue; - for (let k = 0; k < classes.length; k++) { - const { name, value } = classes[k]; - if (!name) continue; - element_to_apply.style.setProperty(name, value); - if (to_remove_class) continue; - const class_to_remove = `${name}-[${value}]`; - if (element.className.includes(class_to_remove)) { - element.className = element.className.replace(class_to_remove, '').trim(); - } - } - } - - if (to_remove_class) { - element.className = element.className.replace(original_class, ''); - } -} - -function init(document, event = undefined) { - // const tag_console = `init : ${event ? `(${event.type})` : ''}`; - // console.time(tag_console); - // console.log(event?.type); - - const elements = document.querySelectorAll('*:not(head, head *)[class]'); - - for (let i = 0; i < elements.length; i++) { - const element = elements[i]; - try { - apply_custom_class(element, element.className); - } catch (error) { - console.error(error); - } - } - - const observer = new MutationObserver(init_observer); - observer.observe(document, { - attributes: true, - attributeFilter: ['class'], - childList: true, - subtree: true, - }); - - // console.timeEnd(tag_console); - - for (let i = 0; i < elements.length; i++) { - const element = elements[i]; - if (0 === element.className.length) { - element.removeAttribute('class'); - } - } - - if (!document.documentElement) return; - if (!document.documentElement.hasAttribute('aria-busy')) return; - - document.documentElement.removeAttribute('aria-busy'); -} - -function init_observer(record) { - // console.time('init_observer'); - const elements = []; - for (let i = 0; i < record.length; i++) { - const { type, target, attributeName } = record[i]; - if ('attributes' === type && 'class' === attributeName) { - // apply_custom_class(target, target.className); - elements.push(target); - } - if (type === 'childList') { - // const elements = target.querySelectorAll('*:not(head, head *)[class]'); - elements.push(...target.querySelectorAll('*:not(head, head *)[class]')); - for (let j = 0; j < elements.length; j++) { - const element = elements[j]; - apply_custom_class(element, element.className); - } - } - } - // console.timeEnd('init_observer'); -} - -const CSS_IN_JS_IN_HTML = { - // apply: apply_custom_class, - init, - fromClassNameToGroups: split_classname_to_classes_groups, -}; - -function retrieve_current_selector(element) { - let limit = 1000; - const selectors = []; - let cursor_index_child; - let cursor_element; - do { - cursor_element = cursor_element?.parentElement ?? element; - if (cursor_element.id) { - selectors.push(cursor_element.id); - continue; - } - const { parentElement, tagName } = cursor_element; - if (parentElement) { - const { children } = parentElement; - cursor_index_child = Array.from(children).indexOf(cursor_element); - if (cursor_index_child > 1) { - selectors.unshift(`${tagName}:nth-child(${cursor_index_child + 1})`); - continue; - } - selectors.unshift(tagName); - continue; - } - selectors.unshift(tagName); - if (!parentElement) break; - if ('HTML' === tagName) break; - if ('BODY' === tagName) break; - } while (limit--); - if (limit < 1) { - console.error('retrieve_current_selector : limit reached'); - return ''; - } - return selectors.join('>'); -} - -window.CSS_IN_JS_IN_HTML = CSS_IN_JS_IN_HTML; diff --git a/src/polyfill.js b/src/polyfill.js deleted file mode 100644 index 593da1a..0000000 --- a/src/polyfill.js +++ /dev/null @@ -1,203 +0,0 @@ -if (!window.getComputedStyle) { - window.getComputedStyle = function (e, t) { - return ( - (this.el = e), - (this.getPropertyValue = function (t) { - var n = /(\-([a-z]){1})/g; - return ( - t === 'float' && (t = 'styleFloat'), - n.test(t) && - (t = t.replace(n, function () { - return arguments[2].toUpperCase(); - })), - e.currentStyle[t] ? e.currentStyle[t] : null - ); - }), - this - ); - }; -} - -if (!Array.isArray) { - Array.isArray = function (arg) { - return Object.prototype.toString.call(arg) === '[object Array]'; - }; -} - -if (!Array.prototype.lastIndexOf) { - Array.prototype.lastIndexOf = function (searchElement /*, fromIndex*/) { - 'use strict'; - - if (this === void 0 || this === null) { - throw new TypeError(); - } - - var n; - var k; - var t = Object(this); - var len = t.length >>> 0; - if (len === 0) { - return -1; - } - - n = len - 1; - if (arguments.length > 1) { - n = Number(arguments[1]); - if (n !== n) { - n = 0; - } else if (n !== 0 && n !== 1 / 0 && n !== -(1 / 0)) { - n = (n > 0 || -1) * Math.floor(Math.abs(n)); - } - } - - for (k = n >= 0 ? Math.min(n, len - 1) : len - Math.abs(n); k >= 0; k--) { - if (k in t && t[k] === searchElement) { - return k; - } - } - return -1; - }; -} - -if (!Array.prototype.reduce) { - Array.prototype.reduce = function (callback, initial) { - var accumulator = initial; - for (var i = 0; i < this.length; i++) { - if (accumulator !== undefined) { - accumulator = callback.call(undefined, accumulator, this[i], i, this); - continue; - } - accumulator = this[i]; - } - return accumulator; - }; -} - -(function (win, doc) { - if (win.addEventListener) return; - - function docHijack(p) { - var old = doc[p]; - doc[p] = function (v) { - return addListen(old(v)); - }; - } - function addEvent(on, fn, self) { - return (self = this).attachEvent(`on${on}`, function (e) { - var e = e || win.event; - e.preventDefault = - e.preventDefault || - function () { - e.returnValue = false; - }; - e.stopPropagation = - e.stopPropagation || - function () { - e.cancelBubble = true; - }; - fn.call(self, e); - }); - } - function addListen(obj, i) { - if ((i = obj.length)) while (i--) obj[i].addEventListener = addEvent; - else obj.addEventListener = addEvent; - return obj; - } - - addListen([doc, win]); - if ('Element' in win) { - win.Element.prototype.addEventListener = addEvent; - return; - } - - doc.attachEvent('onreadystatechange', function () { - addListen(doc.all); - }); - docHijack('getElementsByTagName'); - docHijack('getElementById'); - docHijack('createElement'); - addListen(doc.all); -})(window, document); - -window.matchMedia || - (window.matchMedia = (function () { - 'use strict'; - - var styleMedia = window.styleMedia || window.media; - - if (!styleMedia) { - var style = document.createElement('style'); - var script = document.getElementsByTagName('script')[0]; - var info = null; - - style.type = 'text/css'; - style.id = 'matchmediajs-test'; - - if (!script) { - document.head.appendChild(style); - } else { - script.parentNode.insertBefore(style, script); - } - - info = ('getComputedStyle' in window && window.getComputedStyle(style, null)) || style.currentStyle; - - styleMedia = { - matchMedium: function (media) { - var text = `@media ${media}{ #matchmediajs-test { width: 1px; } }`; - - if (style.styleSheet) { - style.styleSheet.cssText = text; - } else { - style.textContent = text; - } - - return info.width === '1px'; - }, - }; - } - - return function (media) { - return { - matches: styleMedia.matchMedium(media || 'all'), - media: media || 'all', - }; - }; - })()); - -if (!document.querySelectorAll) { - document.querySelectorAll = function (selectors) { - var style = document.createElement('style'); - var elements = []; - var element; - document.documentElement.firstChild.appendChild(style); - document._qsa = []; - - style.styleSheet.cssText = `${selectors}{x-qsa:expression(document._qsa && document._qsa.push(this))}`; - window.scrollBy(0, 0); - style.parentNode.removeChild(style); - - while (document._qsa.length) { - element = document._qsa.shift(); - element.style.removeAttribute('x-qsa'); - elements.push(element); - } - document._qsa = null; - return elements; - }; -} - -if (!document.querySelector) { - document.querySelector = function (selectors) { - var elements = document.querySelectorAll(selectors); - return elements.length ? elements[0] : null; - }; -} - -if (!String.prototype.trim) { - (function () { - var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; - String.prototype.trim = function () { - return this.replace(rtrim, ''); - }; - })(); -} diff --git a/tests/classes.test.js b/tests/classes.test.js deleted file mode 100644 index e3f2acb..0000000 --- a/tests/classes.test.js +++ /dev/null @@ -1,23 +0,0 @@ -const utils = require('./utils'); -const {COLORS, onBeforeAll, onBeforeEach, assert} = utils; - -module.exports = describe('Classes', () => { - beforeAll(async () => { - await onBeforeAll(); - }); - - beforeEach(async () => { - // await onBeforeEach(); - }); - - it('Simple class', async () => { - await assert('#example', 'background-[red]', {backgroundColor: COLORS.red}); - }); - - it('Multiple classes', async () => { - await assert('#example', '{background-[red] color-[white]}', { - backgroundColor: COLORS.red, - color: COLORS.white - }); - }); -}); \ No newline at end of file diff --git a/tests/events.test.js b/tests/events.test.js deleted file mode 100644 index 97d4779..0000000 --- a/tests/events.test.js +++ /dev/null @@ -1,15 +0,0 @@ -const utils = require('./utils'); -const {COLORS, onBeforeAll, onBeforeEach, assert} = utils; - -module.exports = describe('Selectors', () => { - beforeAll(async () => { - await onBeforeAll(); - }); - - beforeEach(async () => { - await onBeforeEach(); - }); - - // TODO - -}); \ No newline at end of file diff --git a/tests/index.html b/tests/index.html deleted file mode 100644 index c5d8600..0000000 --- a/tests/index.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - Document test - - -
-
-
    -
  • One
  • -
  • Two
  • -
  • Three
  • -
-
- - \ No newline at end of file diff --git a/tests/media-queries.test.js b/tests/media-queries.test.js deleted file mode 100644 index 97d4779..0000000 --- a/tests/media-queries.test.js +++ /dev/null @@ -1,15 +0,0 @@ -const utils = require('./utils'); -const {COLORS, onBeforeAll, onBeforeEach, assert} = utils; - -module.exports = describe('Selectors', () => { - beforeAll(async () => { - await onBeforeAll(); - }); - - beforeEach(async () => { - await onBeforeEach(); - }); - - // TODO - -}); \ No newline at end of file diff --git a/tests/selectors.test.js b/tests/selectors.test.js deleted file mode 100644 index 4274a2e..0000000 --- a/tests/selectors.test.js +++ /dev/null @@ -1,87 +0,0 @@ -const utils = require('./utils'); -const {COLORS, onBeforeAll, onBeforeEach, assert} = utils; - -module.exports = describe('Selectors', () => { - beforeAll(async () => { - await onBeforeAll(); - }); - - beforeEach(async () => { - await onBeforeEach(); - }); - - it('Simple selector with simple class', async () => { - await assert('#example-second', '[ul]:background-[red]', {}); - const children = await page.$$('#example-second ul'); - for (const child of children) { - await assert(child, null, {backgroundColor: COLORS.red}); - } - }); - - it('Simple selector with multiple classes', async () => { - await assert('#example-second', '[ul]:background-[red] color-[white]', {}); - const children = await page.$$('#example-second ul'); - for (const child of children) { - await assert(child, null, { - backgroundColor: COLORS.red, - color: COLORS.white - }); - } - }); - - it('Selector ">" with simple class', async () => { - await assert('#example-second', '[> ul]:display-[flex]', {}); - const children = await page.$$('#example-second > ul'); - for (const child of children) { - await assert(child, null, {display: 'flex'}); - } - }); - - it('Selector ">" with multiple classes', async () => { - await assert('#example-second', '[> ul]:{display-[flex] flex-direction-[row]}', {}); - const children = await page.$$('#example-second > ul'); - for (const child of children) { - await assert(child, null, { - display: 'flex', - flexDirection: 'row' - }); - } - }); - - it('Selector "+" with simple class', async () => { - await assert('#example', '[+ div]:justify-content-[space-between]', {}); - const element = await page.$('#example + div'); - await assert(element, null, {justifyContent: 'space-between'}); - }); - - it('Selector "+" with multiple classes', async () => { - await assert('#example', '[+ div]:{justify-content-[space-between] align-items-[center]}', {}); - const element = await page.$('#example + div'); - await assert(element, null, { - justifyContent: 'space-between', - alignItems: 'center' - }); - }); - - it('Selector "~" with simple class', async () => { - await assert('#example', '[~ div]:align-items-[center]', {}); - const children = await page.$$('#example ~ div'); - for (const child of children) { - await assert(child, null, {alignItems: 'center'}); - } - }); - - it('Selector "~" with multiple classes', async () => { - await assert('#example', '[~ div]:{align-items-[center] flex-direction-[row]}', {}); - const children = await page.$$('#example ~ div'); - for (const child of children) { - await assert(child, null, { - alignItems: 'center', - flexDirection: 'row' - }); - } - }); - - // TODO: Tests for @lookout selector - -}); \ No newline at end of file diff --git a/tests/utils.js b/tests/utils.js deleted file mode 100644 index e56468a..0000000 --- a/tests/utils.js +++ /dev/null @@ -1,77 +0,0 @@ -const path = require('path') - -// async function getComputedStyle(element, pseudoElement = null) { -// return page.evaluate((element, pseudoElement) => { -// const style = window.getComputedStyle(element, pseudoElement); -// return JSON.parse(JSON.stringify(style)); -// }, element, pseudoElement); -// } - -const COLORS = { - red: 'rgb(255, 0, 0)', - green: 'rgb(0, 128, 0)', - blue: 'rgb(0, 0, 255)', - white: 'rgb(255, 255, 255)', - black: 'rgb(0, 0, 0)' -}; - -async function onBeforeAll () { - jest.setTimeout(100000) - - const url = 'file://' + path.join(__dirname, 'index.html'); - - await page.goto(url); - await page.addScriptTag({ path: path.join(__dirname, '..', 'build', 'index.min.js') }); - - await page.addScriptTag({ - content: `CSS_IN_JS_IN_HTML.init(document, null);` - }); -} - -async function onBeforeEach() { - await page.reload(); -} - -async function assert(element, classes, styles = {}) { - if ('string' !== typeof element) { - return assert_element(element, classes, styles); - } - - const selector = element; - - const computedStyle = await page.evaluate( async (selector, classes) => { - const element = document.querySelector(selector); - element.className = classes; - await Promise.resolve(setTimeout(() => {}, 1000)); - const computedStyle = await getComputedStyle(element); - return JSON.parse(JSON.stringify(computedStyle)); - }, selector, classes); - - for (const style in styles) { - await expect(computedStyle[style]).toBe(styles[style]); - } -} - -async function assert_element(element, styles, classes = null) { - const computedStyle = await page.evaluate( async (element, classes) => { - if (classes) { - element.className = classes; - await Promise.resolve(setTimeout(() => {}, 1000)); - } - const computedStyle = await getComputedStyle(element); - return JSON.parse(JSON.stringify(computedStyle)); - }, element, classes); - - for (const style in styles) { - await expect(computedStyle[style]).toBe(styles[style]); - } -} - - -module.exports = { - COLORS, - // getComputedStyle, - onBeforeAll, - onBeforeEach, - assert -}; \ No newline at end of file