├── .gitattributes ├── .gitignore ├── .npmrc ├── jest.config.js ├── .babelrc ├── .prettierrc ├── tests ├── fixtures │ ├── lorem-ipsum.twig │ ├── accordion.twig │ └── accordion.js ├── index.js └── __snapshots__ │ └── index.js.snap ├── rollup.config.js ├── LICENSE ├── package.json ├── .github └── workflows │ └── node.js.yml ├── src └── index.js └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | .idea 4 | node_modules 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://registry.npmjs.org/ 2 | package-lock=false 3 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | coverageDirectory: "coverage", 4 | testMatch: ['/tests/*.js'], 5 | testEnvironment: 'jsdom' 6 | }; 7 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "useBuiltIns": "entry", 7 | "corejs": 3 8 | } 9 | ] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": false, 4 | "singleQuote": false, 5 | "tabWidth": 2, 6 | "trailingComma": "es5", 7 | bracketSpacing: true, 8 | jsxBracketSameLine: true 9 | } 10 | -------------------------------------------------------------------------------- /tests/fixtures/lorem-ipsum.twig: -------------------------------------------------------------------------------- 1 | Cras quis nulla commodo, aliquam lectus sed, blandit augue. Cras ullamcorper bibendum bibendum. Duis tincidunt urna non pretium porta. Nam condimentum vitae ligula vel ornare. Phasellus. 2 | -------------------------------------------------------------------------------- /tests/fixtures/accordion.twig: -------------------------------------------------------------------------------- 1 |
2 | {{ title|default('Accordion title')|backwords }} 3 |
4 | {% block accordion_content %} 5 |

{% include "@twig-testing-library-tests/lorem-ipsum.twig" %}

6 | {% endblock %} 7 |
8 |
9 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import babel from 'rollup-plugin-babel'; 4 | 5 | export default { 6 | input: 'src/index.js', 7 | external: [ 8 | 'path', 9 | 'fs', 10 | '@testing-library/dom', 11 | 'twig', 12 | 'regenerator-runtime/runtime', 13 | 'drupal-attribute', 14 | 'core-js/modules/es.array.for-each', 15 | 'core-js/modules/es.array.is-array', 16 | 'core-js/modules/es.array.iterator', 17 | 'core-js/modules/es.object.to-string', 18 | 'core-js/modules/es.promise', 19 | 'core-js/modules/es.set', 20 | 'core-js/modules/es.string.iterator', 21 | 'core-js/modules/web.dom-collections.for-each', 22 | 'core-js/modules/web.dom-collections.iterator' 23 | ], 24 | output: [ 25 | { 26 | file: 'dist/index.js', 27 | format: 'cjs', 28 | exports: 'named', 29 | strict: false 30 | } 31 | ], 32 | plugins: [ 33 | babel({ 34 | presets: [['@babel/preset-env', { 35 | modules: false, 36 | useBuiltIns: 'usage', 37 | corejs: 3, 38 | }]], 39 | babelrc: false, 40 | }), 41 | ], 42 | }; 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Lee Rowlands 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/fixtures/accordion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Accordion 3 | * @file Accordion handler. 4 | */ 5 | 6 | class Accordion { 7 | constructor(obj) { 8 | this.accordion = obj; 9 | this.summary = obj.querySelector('.accordion__title'); 10 | } 11 | 12 | init() { 13 | const open = this.accordion.hasAttribute('open'); 14 | if (open) { 15 | this.accordion.classList.add('accordion--open'); 16 | } 17 | this.summary.addEventListener('focus', () => { 18 | this.handleFocus(); 19 | }); 20 | this.summary.addEventListener('blur', () => { 21 | this.handleBlur(); 22 | }); 23 | this.summary.addEventListener('click', () => { 24 | this.handleClick(); 25 | }); 26 | } 27 | 28 | handleFocus() { 29 | // Focus class for styling. 30 | this.accordion.classList.add('has-focus'); 31 | } 32 | 33 | handleBlur() { 34 | // Focus class for styling. 35 | this.accordion.classList.remove('has-focus'); 36 | } 37 | 38 | handleClick() { 39 | const open = this.accordion.classList.contains('accordion--open'); 40 | this.summary.setAttribute('aria-expanded', !open); 41 | this.summary.setAttribute('aria-pressed', !open); 42 | this.accordion.classList.toggle('accordion--open'); 43 | } 44 | } 45 | 46 | export default { Accordion }; 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twig-testing-library", 3 | "version": "0.0.0-development", 4 | "description": "Simple and complete Twig testing utilities that encourage good testing practices.", 5 | "main": "dist/index.js", 6 | "files": [ 7 | "dist" 8 | ], 9 | "scripts": { 10 | "test": "jest", 11 | "pretest": "npm run-script build", 12 | "build": "rm -rf dist && rollup -c", 13 | "format": "prettier --write \"{test,src}/**/*.js\"", 14 | "lint": "prettier --check \"{test,src}/**/*.js\"", 15 | "coverage": "jest --collect-coverage --collectCoverageFrom=\"src/**/*.js\"", 16 | "semantic-release": "semantic-release" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/larowlan/twig-testing-library.git" 21 | }, 22 | "keywords": [ 23 | "testing", 24 | "ui", 25 | "dom", 26 | "jsdom", 27 | "unit", 28 | "integration", 29 | "functional", 30 | "end-to-end", 31 | "e2e", 32 | "twig" 33 | ], 34 | "author": "Lee Rowlands (https://github.com/larowlan)", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/larowlan/twig-testing-library/issues" 38 | }, 39 | "homepage": "https://github.com/larowlan/twig-testing-library#readme", 40 | "dependencies": { 41 | "@babel/runtime": "^7.14.6", 42 | "@testing-library/dom": "^8.0.0", 43 | "core-js": "^3.15.2", 44 | "drupal-attribute": "^1.0.2", 45 | "twig": "^1.15.4" 46 | }, 47 | "devDependencies": { 48 | "@babel/core": "^7.14.6", 49 | "@babel/preset-env": "^7.14.7", 50 | "babel-jest": "^27.0.6", 51 | "jest": "^27.0.6", 52 | "prettier": "^2.3.2", 53 | "rollup": "^2.52.6", 54 | "rollup-plugin-babel": "^4.4.0", 55 | "rollup-plugin-commonjs": "^10.1.0", 56 | "rollup-plugin-node-resolve": "^5.2.0", 57 | "rollup-plugin-uglify": "^6.0.4", 58 | "semantic-release": "^17.4.4" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: validate 2 | on: 3 | push: 4 | branches: 5 | - 'master' 6 | - 'beta' 7 | pull_request: {} 8 | jobs: 9 | main: 10 | strategy: 11 | matrix: 12 | node: [12, 14] 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: 🛑 Cancel Previous Runs 16 | uses: styfle/cancel-workflow-action@0.9.0 17 | 18 | - name: ⬇️ Checkout repo 19 | uses: actions/checkout@v2 20 | 21 | - name: ⎔ Setup node 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: ${{ matrix.node }} 25 | 26 | - name: 📥 Download deps 27 | uses: bahmutov/npm-install@v1 28 | with: 29 | useLockFile: false 30 | 31 | - name: 🧹 Linting 32 | run: npm run lint 33 | 34 | - name: ✅ Tests 35 | run: npm run test 36 | 37 | release: 38 | needs: main 39 | runs-on: ubuntu-latest 40 | if: 41 | ${{ github.repository == 'larowlan/twig-testing-library' && 42 | contains('refs/heads/master',github.ref) && github.event_name == 'push' }} 43 | steps: 44 | - name: 🛑 Cancel Previous Runs 45 | uses: styfle/cancel-workflow-action@0.9.0 46 | 47 | - name: ⬇️ Checkout repo 48 | uses: actions/checkout@v2 49 | 50 | - name: ⎔ Setup node 51 | uses: actions/setup-node@v2 52 | with: 53 | node-version: 14 54 | 55 | - name: 📥 Download deps 56 | uses: bahmutov/npm-install@v1 57 | with: 58 | useLockFile: false 59 | 60 | - name: 🏗 Run build script 61 | run: npm run build 62 | 63 | - name: 🚀 Release 64 | uses: cycjimmy/semantic-release-action@v2 65 | with: 66 | semantic_version: 17 67 | branches: | 68 | [ 69 | 'master' 70 | ] 71 | env: 72 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 73 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 74 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { getQueriesForElement, prettyDOM } from "@testing-library/dom" 2 | import Twig from "twig" 3 | import fs from "fs" 4 | import "regenerator-runtime/runtime" 5 | import DrupalAttribute from "drupal-attribute" 6 | 7 | const mountedContainers = new Set() 8 | 9 | if (typeof afterEach === "function") { 10 | afterEach(cleanup) 11 | } 12 | 13 | async function render( 14 | twigFile, 15 | context = {}, 16 | namespaces = {}, 17 | twigCallback = () => {} 18 | ) { 19 | const baseElement = document.body 20 | const container = baseElement.appendChild(document.createElement("div")) 21 | 22 | // Add it to the mounted containers to cleanup. 23 | mountedContainers.add(container) 24 | twigCallback(Twig) 25 | 26 | container.innerHTML = await loadTemplate(twigFile, context, namespaces) 27 | 28 | return { 29 | container, 30 | baseElement, 31 | debug: (el = baseElement, maxLength, options) => 32 | Array.isArray(el) 33 | ? // eslint-disable-next-line no-console 34 | el.forEach((e) => console.log(prettyDOM(e, maxLength, options))) 35 | : // eslint-disable-next-line no-console, 36 | console.log(prettyDOM(el, maxLength, options)), 37 | ...getQueriesForElement(baseElement), 38 | } 39 | } 40 | 41 | const loadTemplate = async (file, context = {}, namespaces) => { 42 | Twig.registryReset = () => { 43 | Twig.Templates.registry = {} 44 | } 45 | 46 | Twig.cache(false) 47 | Twig.extendFunction("create_attribute", (value) => { 48 | if (typeof value === "object" && value !== null) { 49 | value = Object.entries(value) 50 | } 51 | return new DrupalAttribute(value) 52 | }) 53 | Twig.twigAsync = (options) => { 54 | return new Promise((resolve, reject) => { 55 | options.load = resolve 56 | options.error = reject 57 | options.async = true 58 | options.autoescape = false 59 | options.namespaces = namespaces 60 | 61 | if (options.data || options.ref) { 62 | try { 63 | resolve(Twig.twig(options)) 64 | } catch (error) { 65 | reject(error) 66 | } 67 | } else { 68 | fs.readFile(options.path, "utf8", (err, data) => { 69 | if (err) { 70 | reject(new Error(`Unable to find template file ${options.path}`)) 71 | return 72 | } 73 | options.load = (template) => { 74 | template.rawMarkup = data 75 | resolve(template) 76 | } 77 | Twig.twig(options) 78 | }) 79 | } 80 | }) 81 | } 82 | return Twig.twigAsync({ 83 | path: file, 84 | }).then((template) => { 85 | if (!context.hasOwnProperty("attributes")) { 86 | context.attributes = new DrupalAttribute() 87 | } 88 | return template.render(context) 89 | }) 90 | } 91 | 92 | function cleanup() { 93 | mountedContainers.forEach(cleanupContainer) 94 | } 95 | 96 | function cleanupContainer(container) { 97 | if (container.parentNode === document.body) { 98 | document.body.removeChild(container) 99 | } 100 | mountedContainers.delete(container) 101 | } 102 | 103 | // just re-export everything from dom-testing-library 104 | export * from "@testing-library/dom" 105 | export { render, cleanup, Twig } 106 | 107 | /* eslint func-name-matching:0 */ 108 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | /* eslint no-new: 0 */ 2 | import Accordion from './fixtures/accordion'; 3 | import {render, fireEvent, Twig} from "../src"; 4 | import DrupalAttribute from "drupal-attribute" 5 | 6 | Twig.extendFilter("backwords", (text) => { 7 | return text.split(" ").reverse().join(" "); 8 | }); 9 | 10 | describe('Test library by testing an accordion', () => { 11 | it('Can be initially rendered open', async () => { 12 | const { container, getByText } = await render('./tests/fixtures/accordion.twig', { 13 | // This is intentionally backwards so we can test extending twig. 14 | title: 'title Accordion', 15 | open: true, 16 | }, { 17 | 'twig-testing-library-tests': './tests/fixtures/' 18 | }); 19 | const accordionElement = container.querySelector('.accordion'); 20 | const summaryElement = accordionElement.querySelector('summary'); 21 | const accordion = new Accordion.Accordion(accordionElement); 22 | accordion.init(); 23 | expect(accordionElement).toMatchSnapshot('Initial render'); 24 | expect(accordionElement.classList.contains('accordion--open')).toBe(true); 25 | fireEvent.click(getByText('Accordion title')); 26 | expect(accordionElement).toMatchSnapshot('First collapse'); 27 | expect(accordionElement.classList.contains('accordion--open')).toBe(false); 28 | expect(summaryElement.getAttribute('aria-expanded')).toEqual('false'); 29 | expect(summaryElement.getAttribute('aria-pressed')).toEqual('false'); 30 | fireEvent.click(getByText('Accordion title')); 31 | expect(accordionElement).toMatchSnapshot('Re-open'); 32 | expect(accordionElement.classList.contains('accordion--open')).toBe(true); 33 | expect(summaryElement.getAttribute('aria-expanded')).toEqual('true'); 34 | expect(summaryElement.getAttribute('aria-pressed')).toEqual('true'); 35 | }); 36 | 37 | it('Can be initially rendered closed', async () => { 38 | const { container, getByText } = await render('./tests/fixtures/accordion.twig', { 39 | // This is intentionally backwards so we can test extending twig. 40 | title: 'title Accordion', 41 | open: false, 42 | }, { 43 | 'twig-testing-library-tests': './tests/fixtures/' 44 | }); 45 | const accordionElement = container.querySelector('.accordion'); 46 | const summaryElement = accordionElement.querySelector('summary'); 47 | const accordion = new Accordion.Accordion(accordionElement); 48 | accordion.init(); 49 | expect(accordionElement).toMatchSnapshot('Initial render'); 50 | expect(accordionElement.classList.contains('accordion--open')).toBe(false); 51 | fireEvent.click(getByText('Accordion title')); 52 | expect(accordionElement).toMatchSnapshot('First expand'); 53 | expect(accordionElement.classList.contains('accordion--open')).toBe(true); 54 | expect(summaryElement.getAttribute('aria-expanded')).toEqual('true'); 55 | expect(summaryElement.getAttribute('aria-pressed')).toEqual('true'); 56 | fireEvent.click(getByText('Accordion title')); 57 | expect(accordionElement).toMatchSnapshot('Re-collapse'); 58 | expect(accordionElement.classList.contains('accordion--open')).toBe(false); 59 | expect(summaryElement.getAttribute('aria-expanded')).toEqual('false'); 60 | expect(summaryElement.getAttribute('aria-pressed')).toEqual('false'); 61 | }); 62 | 63 | it('Can support passing attributes', async () => { 64 | const attributes = new DrupalAttribute() 65 | attributes.set('data-foo', 'bar') 66 | const { container, getByText } = await render('./tests/fixtures/accordion.twig', { 67 | // This is intentionally backwards so we can test extending twig. 68 | title: 'title Accordion', 69 | open: false, 70 | attributes 71 | }, { 72 | 'twig-testing-library-tests': './tests/fixtures/' 73 | }); 74 | const accordionElement = container.querySelector('.accordion'); 75 | expect(accordionElement.dataset.foo).toEqual('bar'); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /tests/__snapshots__/index.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Test library by testing an accordion Can be initially rendered closed: First expand 1`] = ` 4 |
8 | 9 | 10 | 15 | Accordion title 16 | 17 | 18 | 19 |
22 | 23 | 24 |

25 | Cras quis nulla commodo, aliquam lectus sed, blandit augue. Cras ullamcorper bibendum bibendum. Duis tincidunt urna non pretium porta. Nam condimentum vitae ligula vel ornare. Phasellus. 26 | 27 |

28 | 29 | 30 |
31 | 32 | 33 |
34 | `; 35 | 36 | exports[`Test library by testing an accordion Can be initially rendered closed: Initial render 1`] = ` 37 |
40 | 41 | 42 | 45 | Accordion title 46 | 47 | 48 | 49 |
52 | 53 | 54 |

55 | Cras quis nulla commodo, aliquam lectus sed, blandit augue. Cras ullamcorper bibendum bibendum. Duis tincidunt urna non pretium porta. Nam condimentum vitae ligula vel ornare. Phasellus. 56 | 57 |

58 | 59 | 60 |
61 | 62 | 63 |
64 | `; 65 | 66 | exports[`Test library by testing an accordion Can be initially rendered closed: Re-collapse 1`] = ` 67 |
70 | 71 | 72 | 79 | 80 | 81 |
84 | 85 | 86 |

87 | Cras quis nulla commodo, aliquam lectus sed, blandit augue. Cras ullamcorper bibendum bibendum. Duis tincidunt urna non pretium porta. Nam condimentum vitae ligula vel ornare. Phasellus. 88 | 89 |

90 | 91 | 92 |
93 | 94 | 95 |
96 | `; 97 | 98 | exports[`Test library by testing an accordion Can be initially rendered open: First collapse 1`] = ` 99 |
102 | 103 | 104 | 111 | 112 | 113 |
116 | 117 | 118 |

119 | Cras quis nulla commodo, aliquam lectus sed, blandit augue. Cras ullamcorper bibendum bibendum. Duis tincidunt urna non pretium porta. Nam condimentum vitae ligula vel ornare. Phasellus. 120 | 121 |

122 | 123 | 124 |
125 | 126 | 127 |
128 | `; 129 | 130 | exports[`Test library by testing an accordion Can be initially rendered open: Initial render 1`] = ` 131 |
135 | 136 | 137 | 140 | Accordion title 141 | 142 | 143 | 144 |
147 | 148 | 149 |

150 | Cras quis nulla commodo, aliquam lectus sed, blandit augue. Cras ullamcorper bibendum bibendum. Duis tincidunt urna non pretium porta. Nam condimentum vitae ligula vel ornare. Phasellus. 151 | 152 |

153 | 154 | 155 |
156 | 157 | 158 |
159 | `; 160 | 161 | exports[`Test library by testing an accordion Can be initially rendered open: Re-open 1`] = ` 162 |
166 | 167 | 168 | 173 | Accordion title 174 | 175 | 176 | 177 |
180 | 181 | 182 |

183 | Cras quis nulla commodo, aliquam lectus sed, blandit augue. Cras ullamcorper bibendum bibendum. Duis tincidunt urna non pretium porta. Nam condimentum vitae ligula vel ornare. Phasellus. 184 | 185 |

186 | 187 | 188 |
189 | 190 | 191 |
192 | `; 193 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

Twig Testing Library

3 | 4 | 5 | goat 11 | 12 | 13 |

A twig testing utility that allows the same testing ergonomics as React testing library.

14 | 15 |
16 |
17 | 18 |
19 | 20 | 21 | [![Build status](https://github.com/larowlan/twig-testing-library/actions/workflows/node.js.yml/badge.svg)](https://github.com/larowlan/twig-testing-library/actions/workflows/node.js.yml) 22 | [![version][version-badge]][package] [![downloads][downloads-badge]][npmtrends] 23 | [![MIT License][license-badge]][license] 24 | [![PRs Welcome][prs-badge]][prs] 25 | 26 | [![Watch on GitHub][github-watch-badge]][github-watch] 27 | [![Star on GitHub][github-star-badge]][github-star] 28 | [![Tweet][twitter-badge]][twitter] 29 | 30 | 31 | ## Table of Contents 32 | 33 | 34 | 35 | 36 | - [The problem](#the-problem) 37 | - [This solution](#this-solution) 38 | - [Installation](#installation) 39 | - [Examples](#examples) 40 | - [Basic Example](#basic-example) 41 | - [More Examples](#more-examples) 42 | - [Issues](#issues) 43 | - [🐛 Bugs](#-bugs) 44 | - [💡 Feature Requests](#-feature-requests) 45 | - [❓ Questions](#-questions) 46 | - [LICENSE](#license) 47 | 48 | 49 | 50 | ## The problem 51 | 52 | You are working with Twig in a styleguide-driven-development process. You are writing isolated components 53 | that consist of css, twig and Javascript. 54 | You want to be able to test your Javascript in relation to your twig file with maximum isolation. 55 | 56 | ## This solution 57 | 58 | The `Twig Testing Library` is a very lightweight solution based on [Twig JS](https://github.com/twigjs/twig.js) for 59 | testing Twig-based components. It is heavily influenced by similar libraries such as [React Testing Library](https://testing-library.com/docs/react-testing-library/intro). 60 | It provides light utility functions on top of `Twig JS` and [Dom testing library](https://testing-library.com/docs/dom-testing-library/intro) 61 | in a way that encourages better testing practices. 62 | 63 | ## Installation 64 | 65 | This module is distributed via [npm][npm] which is bundled with [node][node] and 66 | should be installed as one of your project's `devDependencies`: 67 | 68 | ``` 69 | npm install --save-dev twig-testing-library 70 | ``` 71 | 72 | You may also be interested in installing `@testing-library/jest-dom` so you can 73 | use [the custom jest matchers](https://github.com/testing-library/jest-dom). 74 | 75 | ## Examples 76 | 77 | ### Basic Example 78 | 79 | ```javascript 80 | // accordion.js 81 | 82 | class Accordion { 83 | constructor(obj) { 84 | this.accordion = obj; 85 | this.summary = obj.querySelector('.accordion__title'); 86 | } 87 | 88 | init() { 89 | const open = this.accordion.hasAttribute('open'); 90 | if (open) { 91 | this.accordion.classList.add('accordion--open'); 92 | } 93 | this.summary.addEventListener('focus', () => { 94 | this.handleFocus(); 95 | }); 96 | this.summary.addEventListener('blur', () => { 97 | this.handleBlur(); 98 | }); 99 | this.summary.addEventListener('click', () => { 100 | this.handleClick(); 101 | }); 102 | } 103 | 104 | handleFocus() { 105 | // Focus class for styling. 106 | this.accordion.classList.add('has-focus'); 107 | } 108 | 109 | handleBlur() { 110 | // Focus class for styling. 111 | this.accordion.classList.remove('has-focus'); 112 | } 113 | 114 | handleClick() { 115 | const open = this.accordion.classList.contains('accordion--open'); 116 | this.summary.setAttribute('aria-expanded', !open); 117 | this.summary.setAttribute('aria-pressed', !open); 118 | this.accordion.classList.toggle('accordion--open'); 119 | } 120 | } 121 | 122 | export default { Accordion }; 123 | ``` 124 | 125 | ```javascript 126 | // __tests__/accordion.js 127 | import { render, fireEvent, Twig } from 'twig-testing-library' 128 | 129 | // Add Twig extensions - see the Twig.js wiki. 130 | Twig.extendFilter("backwords", function(value) { 131 | return value.split(" ").reverse().join(" "); 132 | }); 133 | 134 | describe('Accordion toggling', () => { 135 | it('Can be rendered open, and then collapsed on click', async () => { 136 | // Rendering is async, so you need to use await. 137 | const { container, getByText } = await render( 138 | // Path to twig template. 139 | 'accordion.twig', 140 | // Template variables/context. 141 | { 142 | title: 'Accordion title', 143 | open: true, 144 | }, 145 | // Namespace support 146 | { 147 | 'my_namespace': './some/path' 148 | }); 149 | const accordionElement = container.querySelector('.accordion'); 150 | const accordion = new Accordion.Accordion(accordionElement); 151 | accordion.init(); 152 | // Snapshot support via jest. 153 | expect(accordionElement).toMatchSnapshot('Initial render'); 154 | expect(accordionElement.classList.contains('accordion--open')).toBe(true); 155 | fireEvent.click(getByText('Accordion title')); 156 | expect(accordionElement).toMatchSnapshot('First collapse'); 157 | expect(accordionElement.classList.contains('accordion--open')).toBe(false); 158 | }) 159 | }) 160 | ``` 161 | 162 | ### More Examples 163 | 164 | - Refer to the [Dom testing library docs](https://testing-library.com/docs/dom-testing-library/example-intro), we're really just adding the ability to render twig templates on top of that. 165 | 166 | ## Issues 167 | 168 | ### 🐛 Bugs 169 | 170 | Please file an issue for bugs, missing documentation, or unexpected behavior. 171 | 172 | [**See Bugs**][bugs] 173 | 174 | ### 💡 Feature Requests 175 | 176 | Please file an issue to suggest new features. Vote on feature requests by adding 177 | a 👍. This helps maintainers prioritize what to work on. 178 | 179 | [**See Feature Requests**][requests] 180 | 181 | ### ❓ Questions 182 | 183 | For questions related to using the library, please visit a support community 184 | instead of filing an issue on GitHub. 185 | 186 | - [Drupal Slack #frontend channel](https://drupal.org/slack) 187 | 188 | ## LICENSE 189 | 190 | [MIT](LICENSE) 191 | 192 | 193 | 194 | [npm]: https://www.npmjs.com/ 195 | [node]: https://nodejs.org 196 | [version-badge]: https://img.shields.io/npm/v/twig-testing-library.svg?style=flat-square 197 | [package]: https://www.npmjs.com/package/twig-testing-library 198 | [downloads-badge]: https://img.shields.io/npm/dm/twig-testing-library.svg?style=flat-square 199 | [npmtrends]: http://www.npmtrends.com/twig-testing-library 200 | [license-badge]: https://img.shields.io/npm/l/twig-testing-library.svg?style=flat-square 201 | [license]: https://github.com/larowlan/twig-testing-library/blob/master/LICENSE 202 | [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square 203 | [prs]: http://makeapullrequest.com 204 | [github-watch-badge]: https://img.shields.io/github/watchers/larowlan/twig-testing-library.svg?style=social 205 | [github-watch]: https://github.com/larowlan/twig-testing-library/watchers 206 | [github-star-badge]: https://img.shields.io/github/stars/larowlan/twig-testing-library.svg?style=social 207 | [github-star]: https://github.com/larowlan/twig-testing-library/stargazers 208 | [twitter]: https://twitter.com/intent/tweet?text=Check%20out%20twig-testing-library%20by%20%40larowlan%20https%3A%2F%2Fgithub.com%2Flarowlan%2Ftwig-testing-library%20%F0%9F%91%8D 209 | [twitter-badge]: https://img.shields.io/twitter/url/https/github.com/larowlan/twig-testing-library.svg?style=social 210 | [bugs]: https://github.com/larowlan/twig-testing-library/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Acreated-desc 211 | [requests]: https://github.com/larowlan/twig-testing-library/issues?q=is%3Aissue+sort%3Areactions-%2B1-desc+label%3Aenhancement+is%3Aopen 212 | [good-first-issue]: https://github.com/larowlan/twig-testing-library/issues?utf8=✓&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3A"good+first+issue"+ 213 | 214 | 215 | --------------------------------------------------------------------------------