├── .editorconfig ├── .github ├── dependabot.yml ├── scripts │ └── updateDeps.mjs └── workflows │ ├── expense.yml │ ├── test.yaml │ └── update.yml ├── .gitignore ├── .nvmrc ├── LICENSE ├── README.md ├── enhance ├── .arc ├── .gitignore ├── app │ ├── elements │ │ ├── my-header.mjs │ │ └── my-header.test.js │ └── pages │ │ └── index.html ├── package.json ├── prefs.arc ├── public │ ├── axol-welcome.svg │ ├── discord-logo.svg │ ├── enhance-type.svg │ ├── favicon.svg │ └── styles.css └── wdio.conf.js ├── framework-comparisons ├── .gitignore ├── README.md ├── cypress.config.ts ├── cypress │ └── support │ │ ├── component-index.html │ │ └── component.ts ├── index.html ├── package-lock.json ├── package.json ├── playwright.config.ts ├── playwright │ ├── index.html │ └── index.ts ├── public │ └── vite.svg ├── src │ ├── App.css │ ├── App.tsx │ ├── assets │ │ └── react.svg │ ├── components │ │ ├── Card.cy.tsx │ │ ├── Card.pw.tsx │ │ ├── Card.test.tsx │ │ ├── Card.tsx │ │ ├── Card.wdio.tsx │ │ ├── Input.cy.tsx │ │ ├── Input.test.tsx │ │ ├── Input.tsx │ │ ├── Input.wdio.tsx │ │ ├── InstantSearch.css │ │ ├── InstantSearch.test.tsx │ │ ├── InstantSearch.tsx │ │ ├── InstantSearch.wdio.tsx │ │ ├── Zoom.cy.tsx │ │ ├── Zoom.pw.tsx │ │ ├── Zoom.tsx │ │ ├── Zoom.wdio.tsx │ │ ├── __fixtures__ │ │ │ └── algolia.json │ │ └── input.pw.tsx │ ├── constants.ts │ ├── index.css │ ├── main.tsx │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts ├── vitest.conf.ts └── wdio.conf.ts ├── ionic-react ├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── .vscode │ └── extensions.json ├── capacitor.config.ts ├── index.html ├── ionic.config.json ├── package-lock.json ├── package.json ├── public │ ├── favicon.png │ └── manifest.json ├── resources │ ├── icon.png │ └── splash.png ├── src │ ├── App.test.tsx │ ├── App.tsx │ ├── components │ │ ├── ExploreContainer.css │ │ └── ExploreContainer.tsx │ ├── main.tsx │ ├── pages │ │ ├── Tab1.css │ │ ├── Tab1.tsx │ │ ├── Tab2.css │ │ ├── Tab2.tsx │ │ ├── Tab3.css │ │ └── Tab3.tsx │ ├── theme │ │ └── variables.css │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wdio.conf.ts ├── lit-typescript-vite ├── .gitignore ├── index.html ├── package-lock.json ├── package.json ├── public │ └── vite.svg ├── src │ ├── assets │ │ └── lit.svg │ ├── index.css │ ├── my-element.ts │ ├── tests │ │ └── my-element.test.ts │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wdio.conf.ts ├── nextjs ├── .gitignore ├── README.md ├── eslint.config.mjs ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public │ ├── file.svg │ ├── globe.svg │ ├── next.svg │ ├── vercel.svg │ └── window.svg ├── src │ └── app │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── page.tsx ├── tailwind.config.ts ├── tsconfig.json └── wdio.conf.mts ├── nuxt ├── .env.example ├── .gitignore ├── .npmrc ├── README.md ├── app.config.ts ├── app.vue ├── assets │ └── css │ │ └── main.css ├── components │ ├── AppAlerts.vue │ ├── AppFileUpload.vue │ ├── Date.test.ts │ ├── Date.vue │ ├── FormField.vue │ ├── Money.test.ts │ ├── Money.vue │ ├── NavBar.vue │ ├── Pagination.vue │ ├── ProjectCard.vue │ ├── ProjectPledgeForm.vue │ ├── ProjectsDetails.vue │ ├── ProjectsForm.vue │ ├── ProjectsList.vue │ └── TimeAgo.vue ├── composables │ ├── useAlerts.ts │ ├── useCategories.ts │ ├── useFilePreview.ts │ ├── usePaginator.ts │ ├── useProjects.ts │ └── useTypedSupabaseClient.ts ├── middleware │ └── auth.ts ├── nuxt.config.ts ├── package-lock.json ├── package.json ├── pages │ ├── categories │ │ └── [uuid].vue │ ├── connect-wallet.vue │ ├── index.vue │ ├── login.vue │ ├── logout.vue │ ├── profile.vue │ ├── projects │ │ ├── [uuid].vue │ │ └── create.vue │ └── register.vue ├── public │ └── favicon.ico ├── server │ └── tsconfig.json ├── supabase │ ├── .gitignore │ ├── config.toml │ ├── functions │ │ └── .vscode │ │ │ └── settings.json │ ├── migrations │ │ └── 20230717154337_initial_schema.sql │ ├── schema.ts │ └── seed.sql ├── tailwind.config.js ├── tsconfig.json ├── types │ └── index.ts ├── utils │ └── index.ts ├── wdio.conf.ts └── yarn.lock ├── preact-typescript-vite ├── .gitignore ├── index.html ├── package-lock.json ├── package.json ├── public │ └── vite.svg ├── src │ ├── app.css │ ├── app.tsx │ ├── assets │ │ └── preact.svg │ ├── index.css │ ├── main.tsx │ ├── tests │ │ └── HelloWorld.test.tsx │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wdio.conf.ts ├── react-typescript-vite ├── .gitignore ├── index.html ├── package-lock.json ├── package.json ├── public │ └── vite.svg ├── src │ ├── App.css │ ├── App.tsx │ ├── assets │ │ └── react.svg │ ├── components │ │ ├── Button.tsx │ │ ├── InputField.tsx │ │ └── LoginForm.tsx │ ├── index.css │ ├── main.tsx │ ├── tests │ │ ├── HelloWorld.test.tsx │ │ └── LoginForm.test.tsx │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wdio.conf.ts ├── solidjs-typescript-vite ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── App.module.css │ ├── App.tsx │ ├── assets │ │ └── favicon.ico │ ├── components │ │ ├── counter.tsx │ │ ├── modal.css │ │ └── modal.tsx │ ├── directives │ │ └── click-outside.tsx │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ └── tests │ │ ├── counter.test.tsx │ │ ├── example.test.tsx │ │ └── modal.test.tsx ├── tsconfig.json ├── vite.config.ts ├── wdio.conf.ts └── wdio.sauce.conf.ts ├── stencil-component-starter ├── .editorconfig ├── .gitignore ├── .prettierrc.json ├── LICENSE ├── __snapshots__ │ └── MyComponent-chrome-1200x1551-dpr-2.png ├── package-lock.json ├── package.json ├── readme.md ├── src │ ├── components.d.ts │ ├── components │ │ └── my-component │ │ │ ├── my-component.css │ │ │ ├── my-component.test.tsx │ │ │ ├── my-component.tsx │ │ │ └── readme.md │ ├── index.html │ ├── index.ts │ └── utils │ │ ├── utils.test.ts │ │ └── utils.ts ├── stencil.config.ts ├── tsconfig.json └── wdio.conf.ts ├── svelte-typescript-vite ├── .gitignore ├── .vscode │ └── extensions.json ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public │ └── vite.svg ├── src │ ├── App.svelte │ ├── app.css │ ├── assets │ │ └── svelte.svg │ ├── lib │ │ ├── CircleDrawer.svelte │ │ ├── Counter.svelte │ │ ├── Hoverable.svelte │ │ └── Longpress.svelte │ ├── main.ts │ ├── tests │ │ ├── CircleDrawer.test.ts │ │ ├── Counter.test.ts │ │ ├── Hoverable.test.ts │ │ ├── Longpress.test.ts │ │ └── __fixtures__ │ │ │ └── Hoverable.svelte │ └── vite-env.d.ts ├── svelte.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wdio.conf.ts └── vue-typescript-vite ├── .gitignore ├── .vscode └── extensions.json ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public └── vite.svg ├── src ├── App.vue ├── assets │ └── vue.svg ├── components │ ├── CircleDrawer.vue │ └── HelloWorld.vue ├── main.ts ├── style.css ├── tests │ ├── CircleDrawer.test.ts │ └── HelloWorld.test.ts └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wdio.conf.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | indent_style = space 10 | indent_size = 2 11 | 12 | end_of_line = lf 13 | charset = utf-8 14 | trim_trailing_whitespace = true 15 | insert_final_newline = true 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | 4 | updates: 5 | - package-ecosystem: npm 6 | directory: "/" 7 | schedule: 8 | interval: weekly 9 | time: "11:00" 10 | open-pull-requests-limit: 10 11 | versioning-strategy: increase-if-necessary 12 | 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | schedule: 16 | interval: "weekly" 17 | time: "11:00" 18 | -------------------------------------------------------------------------------- /.github/scripts/updateDeps.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs/promises' 2 | import url from 'node:url' 3 | import path from 'node:path' 4 | import cp from 'node:child_process' 5 | 6 | const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) 7 | const root = path.resolve(__dirname, '..', '..') 8 | 9 | function exec (command, framework) { 10 | const prefix = framework ? `[${framework}]: ` : '' 11 | const cwd = framework 12 | ? path.resolve(root, framework) 13 | : process.cwd() 14 | 15 | return new Promise((resolve, reject) => { 16 | console.log(`▶️ ${prefix}${command}`); 17 | const res = cp.exec(command, { cwd }, (err, stdout) => { 18 | if (err) return reject(err) 19 | console.log(prefix + stdout); 20 | return resolve() 21 | }) 22 | }) 23 | } 24 | 25 | const hasNCUInstalled = await exec('which ncu').then(() => true, () => false) 26 | if (!hasNCUInstalled) { 27 | throw new Error(`Missing global dependency "ncu", please install via "npm i -g npm-check-updates"`) 28 | } 29 | 30 | const frameworkDirs = await Promise.all((await fs.readdir(root)) 31 | .filter((entry) => !entry.startsWith('.')) 32 | .map(async (entry) => [entry, (await fs.stat(entry)).isDirectory()]) 33 | ).then((res) => res 34 | .filter(([, isDirectory]) => isDirectory) 35 | .map(([entry]) => entry) 36 | .filter((entry) => ( 37 | !entry.startsWith('framework-comparison') && 38 | /** 39 | * fails update due to: 40 | * npm ERR! ERESOLVE unable to resolve dependency tree 41 | * npm ERR! 42 | * npm ERR! While resolving: ionic-react@0.0.1 43 | * npm ERR! Found: react-router@6.21.2 44 | * npm ERR! node_modules/react-router 45 | * npm ERR! react-router@"^6.21.2" from the root project 46 | * npm ERR! 47 | * npm ERR! Could not resolve dependency: 48 | * npm ERR! peer react-router@"^5.0.1" from @ionic/react-router@7.6.4 49 | * npm ERR! node_modules/@ionic/react-router 50 | * npm ERR! @ionic/react-router@"^7.6.4" from the root project 51 | */ 52 | entry !== 'ionic-react' 53 | ))) 54 | 55 | await Promise.all(frameworkDirs.map(async (framework) => { 56 | console.log(`🪄 Update dependencies for ${framework}`) 57 | await fs.unlink(path.join(root, framework, 'node_modules')) 58 | .catch((err) => { /* ignore */ }) 59 | await exec(`rm -fr ./node_modules package-lock.json`, framework) 60 | await exec(`ncu -u`, framework) 61 | await exec(`npm i`, framework) 62 | })) 63 | -------------------------------------------------------------------------------- /.github/workflows/expense.yml: -------------------------------------------------------------------------------- 1 | name: Expense Contribution 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | prNumber: 7 | description: "Number of the PR (without #)" 8 | required: true 9 | amount: 10 | description: "The expense amount you like to grant for the contribution in $" 11 | required: true 12 | type: choice 13 | options: 14 | - 15 15 | - 25 16 | - 35 17 | - 50 18 | - 100 19 | - 150 20 | - 200 21 | - 250 22 | - 300 23 | - 350 24 | - 400 25 | - 450 26 | - 500 27 | - 550 28 | - 600 29 | - 650 30 | - 700 31 | - 750 32 | - 800 33 | - 850 34 | - 900 35 | - 950 36 | - 1000 37 | 38 | jobs: 39 | authorize: 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: octokit/request-action@v2.3.1 43 | with: 44 | route: GET /orgs/:organisation/teams/:team/memberships/${{ github.actor }} 45 | team: technical-steering-committee 46 | organisation: webdriverio 47 | env: 48 | GITHUB_TOKEN: ${{ secrets.WDIO_BOT_GITHUB_TOKEN }} 49 | expense: 50 | permissions: 51 | contents: write 52 | id-token: write 53 | needs: [authorize] 54 | runs-on: ubuntu-latest 55 | steps: 56 | - name: Run Expense Flow 57 | uses: webdriverio/expense-action@v1 58 | with: 59 | prNumber: ${{ github.event.inputs.prNumber }} 60 | amount: ${{ github.event.inputs.amount }} 61 | env: 62 | RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} 63 | GH_TOKEN: ${{ secrets.WDIO_BOT_GITHUB_TOKEN }} 64 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | exampleDir: 9 | - lit-typescript-vite 10 | - preact-typescript-vite 11 | - react-typescript-vite 12 | - svelte-typescript-vite 13 | - vue-typescript-vite 14 | - solidjs-typescript-vite 15 | - enhance 16 | # disable due to: https://github.com/webdriverio/webdriverio/issues/14160 17 | # - nuxt 18 | # - nextjs 19 | - stencil-component-starter 20 | - ionic-react 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | - uses: actions/setup-node@v4 25 | with: 26 | node-version: 18 27 | - name: Install 28 | run: npm install 29 | working-directory: ${{ matrix.exampleDir }} 30 | - name: Test 31 | run: npm run wdio 32 | working-directory: ${{ matrix.exampleDir }} 33 | - uses: actions/upload-artifact@v4 34 | if: failure() 35 | with: 36 | name: logs 37 | path: ${{ matrix.exampleDir }}/logs 38 | -------------------------------------------------------------------------------- /.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | # this workflow merges requests from Dependabot if tests are passing 2 | # ref https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions 3 | # and https://github.com/dependabot/fetch-metadata 4 | name: Auto-merge 5 | 6 | # `pull_request_target` means this uses code in the base branch, not the PR. 7 | on: pull_request_target 8 | 9 | # Dependabot PRs' tokens have read permissions by default and thus we must enable write permissions. 10 | permissions: 11 | contents: write 12 | pull-requests: write 13 | 14 | jobs: 15 | dependencies: 16 | runs-on: ubuntu-latest 17 | if: github.actor == 'dependabot[bot]' 18 | 19 | steps: 20 | - name: Fetch PR metadata 21 | id: metadata 22 | uses: dependabot/fetch-metadata@v2.1.0 23 | with: 24 | github-token: ${{ secrets.GITHUB_TOKEN }} 25 | 26 | - name: Wait for PR CI 27 | # Don't merge updates to GitHub Actions versions automatically. 28 | # (Some repos may wish to limit by version range (major/minor/patch), or scope (dep vs dev-dep), too.) 29 | if: contains(steps.metadata.outputs.package-ecosystem, 'npm') 30 | uses: lewagon/wait-on-check-action@v1.3.4 31 | with: 32 | ref: ${{ github.event.pull_request.head.sha }} 33 | repo-token: ${{ secrets.GITHUB_TOKEN }} 34 | wait-interval: 30 # seconds 35 | running-workflow-name: dependencies # wait for all checks except this one 36 | allowed-conclusions: success # all other checks must pass, being skipped or cancelled is not sufficient 37 | 38 | - name: Auto-merge dependabot PRs 39 | # Don't merge updates to GitHub Actions versions automatically. 40 | # (Some repos may wish to limit by version range (major/minor/patch), or scope (dep vs dev-dep), too.) 41 | if: contains(steps.metadata.outputs.package-ecosystem, 'npm') 42 | env: 43 | PR_URL: ${{ github.event.pull_request.html_url }} 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | # The "auto" flag will only merge once all of the target branch's required checks 46 | # are met. Configure those in the "branch protection" settings for each repo. 47 | run: gh pr merge --auto --squash "$PR_URL" 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | /framework-comparisons/test-results/ 106 | /framework-comparisons/playwright-report/ 107 | /framework-comparisons/playwright/.cache/ 108 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18.12.0 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) OpenJS Foundation 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | 'Software'), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /enhance/.arc: -------------------------------------------------------------------------------- 1 | @app 2 | myproj 3 | 4 | @plugins 5 | enhance/arc-plugin-enhance 6 | -------------------------------------------------------------------------------- /enhance/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .DS_Store 3 | node_modules 4 | public/static.json 5 | public/bundles 6 | sam.json 7 | sam.yaml 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /enhance/app/elements/my-header.mjs: -------------------------------------------------------------------------------- 1 | export default function MyHeader ({ html }) { 2 | return html` 3 |
4 |
5 | 6 |
7 |
8 | 9 |
10 |
11 | ` 12 | } -------------------------------------------------------------------------------- /enhance/app/elements/my-header.test.js: -------------------------------------------------------------------------------- 1 | import { expect, browser } from '@wdio/globals' 2 | import enhance from '@enhance/ssr' 3 | 4 | import MyHeader from './my-header.mjs' 5 | 6 | describe('Enhance Framework', () => { 7 | it('should render MyHeader element correctly', async () => { 8 | const html = enhance({ 9 | elements: { 10 | 'my-header': MyHeader 11 | } 12 | }) 13 | const actual = document.createElement('div') 14 | actual.innerHTML = (html``).replace(/<(\/*)(html|head|body)>/g, '') 15 | document.body.appendChild(actual) 16 | expect(await browser.$$('img').length).toBe(2) 17 | }) 18 | }) 19 | 20 | -------------------------------------------------------------------------------- /enhance/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "sandbox", 4 | "wdio": "wdio run ./wdio.conf.js" 5 | }, 6 | "devDependencies": { 7 | "@architect/sandbox": "latest", 8 | "@enhance/ssr": "^4.0.3", 9 | "@wdio/browser-runner": "^9.7.3", 10 | "@wdio/cli": "^9.7.3", 11 | "@wdio/mocha-framework": "^9.7.3", 12 | "@wdio/spec-reporter": "^9.6.3" 13 | }, 14 | "dependencies": { 15 | "@enhance/arc-plugin-enhance": "latest" 16 | }, 17 | "type": "module", 18 | "eslintConfig": { 19 | "env": { 20 | "node": true, 21 | "mocha": true, 22 | "browser": true 23 | }, 24 | "extends": "eslint:recommended", 25 | "rules": { 26 | "indent": [ 27 | "error", 28 | 2 29 | ] 30 | }, 31 | "ignorePatterns": [], 32 | "parserOptions": { 33 | "sourceType": "module", 34 | "ecmaVersion": 2022 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /enhance/prefs.arc: -------------------------------------------------------------------------------- 1 | @sandbox 2 | livereload true 3 | -------------------------------------------------------------------------------- /framework-comparisons/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | cypress/screenshots 26 | cypress/videos 27 | -------------------------------------------------------------------------------- /framework-comparisons/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress' 2 | 3 | export default defineConfig({ 4 | component: { 5 | devServer: { 6 | framework: 'react', 7 | bundler: 'vite' 8 | } 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /framework-comparisons/cypress/support/component-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Components App 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /framework-comparisons/cypress/support/component.ts: -------------------------------------------------------------------------------- 1 | import { mount } from 'cypress/react18' 2 | import 'cypress-real-events'; 3 | 4 | // Ensure global app styles are loaded: 5 | import '../../src/index.css'; 6 | 7 | // Augment the Cypress namespace to include type definitions for 8 | // your custom command. 9 | // Alternatively, can be defined in cypress/support/component.d.ts 10 | // with a at the top of your spec. 11 | declare global { 12 | namespace Cypress { 13 | interface Chainable { 14 | mount: typeof mount 15 | } 16 | } 17 | } 18 | 19 | Cypress.Commands.add('mount', mount) 20 | -------------------------------------------------------------------------------- /framework-comparisons/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /framework-comparisons/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsdom-shortcomings", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "cypress:run": "cypress run --component", 10 | "playwright:run": "playwright test --config ./playwright.config.ts", 11 | "preview": "vite preview", 12 | "test": "vitest -c ./vitest.conf.ts", 13 | "test:ui": "vitest -c ./vitest.conf.ts --ui", 14 | "coverage": "vitest run -c ./vitest.conf.ts --coverage", 15 | "wdio": "wdio run ./wdio.conf.ts" 16 | }, 17 | "dependencies": { 18 | "algoliasearch": "^4.19.1", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "react-instantsearch-dom": "^6.40.4" 22 | }, 23 | "devDependencies": { 24 | "@playwright/experimental-ct-react": "^1.37.1", 25 | "@playwright/test": "^1.37.1", 26 | "@testing-library/jest-dom": "^6.1.1", 27 | "@testing-library/react": "^14.0.0", 28 | "@types/react": "^18.2.21", 29 | "@types/react-dom": "^18.2.7", 30 | "@types/react-instantsearch-dom": "^6.12.3", 31 | "@vitejs/plugin-react": "^4.0.4", 32 | "@wdio/browser-runner": "^8.15.6", 33 | "@wdio/cli": "^8.15.6", 34 | "@wdio/mocha-framework": "^8.15.6", 35 | "@wdio/spec-reporter": "^8.15.6", 36 | "cypress": "^12.17.4", 37 | "cypress-real-events": "^1.10.0", 38 | "jsdom": "^22.1.0", 39 | "playwright": "^1.37.1", 40 | "react-zoom-pan-pinch": "^3.1.0", 41 | "ts-node": "^10.9.1", 42 | "typescript": "^5.1.6", 43 | "vite": "^4.4.9", 44 | "vitest": "^0.34.2" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /framework-comparisons/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@playwright/test'; 2 | export default defineConfig({ 3 | testMatch: /.*\.pw\.tsx/, 4 | use: { 5 | headless: false, 6 | viewport: { width: 1280, height: 720 }, 7 | ignoreHTTPSErrors: true, 8 | video: 'on-first-retry', 9 | }, 10 | }) 11 | -------------------------------------------------------------------------------- /framework-comparisons/playwright/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /framework-comparisons/playwright/index.ts: -------------------------------------------------------------------------------- 1 | import '../src/index.css' 2 | -------------------------------------------------------------------------------- /framework-comparisons/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /framework-comparisons/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .testbed { 37 | padding: 2em; 38 | position: relative; 39 | overflow: hidden; 40 | } 41 | 42 | .read-the-docs { 43 | color: #888; 44 | } 45 | -------------------------------------------------------------------------------- /framework-comparisons/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Card, SideEffect } from "./components/Card" 2 | import reactLogo from './assets/react.svg' 3 | 4 | import './App.css' 5 | 6 | function App() { 7 | return ( 8 |
9 |
10 | 11 | Vite logo 12 | 13 | 14 | React logo 15 | 16 |
17 |

Vite + React

18 | 19 |

20 | Click on the Vite and React logos to learn more 21 |

22 |
23 | ) 24 | } 25 | 26 | export default App 27 | -------------------------------------------------------------------------------- /framework-comparisons/src/components/Card.cy.tsx: -------------------------------------------------------------------------------- 1 | import { Card, SideEffect } from './Card'; 2 | 3 | describe('Card Component', () => { 4 | it('can be clicked without a side effect', () => { 5 | cy.mount() 6 | cy.contains('count is 0') 7 | cy.get('button').click() 8 | cy.contains('count is 1') 9 | }) 10 | 11 | it('fails because element is not visible', () => { 12 | cy.mount() 13 | cy.contains('count is 0') 14 | /** 15 | * fails due to: 16 | * ` 17 | * This element `` 29 | * This element `` 40 | * is being covered by another element: 41 | * `
You can...
42 | */ 43 | cy.get('button').click() 44 | cy.contains('count is 1') 45 | }) 46 | 47 | it('passes as Cypress magically puts the button into position', () => { 48 | cy.mount() 49 | cy.contains('count is 0') 50 | cy.get('button').click() 51 | cy.contains('count is 1') 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /framework-comparisons/src/components/Card.pw.tsx: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/experimental-ct-react' 2 | import { Card } from './Card' 3 | 4 | test.use({ viewport: { width: 500, height: 500 } }) 5 | 6 | test.skip('Card Component: can be clicked without a side effect', async ({ mount }) => { 7 | const component = await mount() 8 | await expect(component).toContainText('count is 0') 9 | await component.getByText('count is 0').click() 10 | await expect(component).toContainText('count is 1') 11 | }) 12 | 13 | test.skip('Card Component: fails because element is not visible', async ({ mount }) => { 14 | const component = await mount() 15 | await expect(component).toContainText('count is 0') 16 | /** 17 | * times out due to: element is not visible - waiting... 18 | */ 19 | await component.getByText('count is 0').click() 20 | await expect(component).toContainText('count is 1') 21 | }) 22 | 23 | test.skip('Card Component: fails because element has zero height', async ({ mount }) => { 24 | const component = await mount() 25 | await expect(component).toContainText('count is 0') 26 | /** 27 | * times out due to: element is not visible - waiting... 28 | */ 29 | await component.getByText('count is 0').click() 30 | await expect(component).toContainText('count is 1') 31 | }) 32 | 33 | test.skip('Card Component: fails because another element is laying over the button', async ({ mount }) => { 34 | const component = await mount() 35 | await expect(component).toContainText('count is 0') 36 | /** 37 | * times out due to:
You can't click it!
intercepts pointer events 38 | */ 39 | await component.getByText('count is 0').click() 40 | await expect(component).toContainText('count is 1') 41 | }) 42 | 43 | test.skip('Card Component: passes as Playwright magically puts the button into position', async ({ mount }) => { 44 | const component = await mount() 45 | await expect(component).toContainText('count is 0') 46 | await component.getByText('count is 0').click() 47 | await expect(component).toContainText('count is 1') 48 | }) 49 | -------------------------------------------------------------------------------- /framework-comparisons/src/components/Card.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "vitest" 2 | import { render, fireEvent } from '@testing-library/react' 3 | import '@testing-library/jest-dom' 4 | 5 | import { Card, SideEffect } from '../components/Card' 6 | 7 | describe('Card Component', () => { 8 | it('can be clicked without a side effect', () => { 9 | const { getByText, getByLabelText } = render() 10 | expect(getByText('count is 0')).toBeInTheDocument() 11 | fireEvent.click(getByLabelText('counter')) 12 | expect(getByText('count is 1')).toBeInTheDocument() 13 | }) 14 | 15 | it('can still be clicked with visibility side effect', () => { 16 | const { getByText, getByLabelText } = render() 17 | expect(getByText('count is 0')).toBeInTheDocument() 18 | fireEvent.click(getByLabelText('counter')) 19 | expect(getByText('count is 1')).toBeInTheDocument() 20 | }) 21 | 22 | it('can still be clicked with zero height side effect', () => { 23 | const { getByText, getByLabelText } = render() 24 | expect(getByText('count is 0')).toBeInTheDocument() 25 | fireEvent.click(getByLabelText('counter')) 26 | expect(getByText('count is 1')).toBeInTheDocument() 27 | }) 28 | 29 | it('can still be clicked with an overlaying element side effect', () => { 30 | const { getByText, getByLabelText } = render() 31 | expect(getByText('count is 0')).toBeInTheDocument() 32 | fireEvent.click(getByLabelText('counter')) 33 | expect(getByText('count is 1')).toBeInTheDocument() 34 | }) 35 | 36 | it('can still be clicked with an out of bounce side effect', () => { 37 | const { getByText, getByLabelText } = render() 38 | expect(getByText('count is 0')).toBeInTheDocument() 39 | fireEvent.click(getByLabelText('counter')) 40 | expect(getByText('count is 1')).toBeInTheDocument() 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /framework-comparisons/src/components/Card.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | export enum SideEffect { 4 | ZERO_HEIGHT = 1, 5 | INVISIBLE, 6 | OVERLAYING_ELEMENT, 7 | OUT_OF_BOUNDS 8 | } 9 | 10 | interface Props { 11 | sideEffect?: SideEffect 12 | } 13 | 14 | export const Card: React.FC = (props?: Props) => { 15 | const [count, setCount] = useState(0) 16 | const style: React.ButtonHTMLAttributes = props?.sideEffect === SideEffect.INVISIBLE 17 | ? { visibility: 'hidden' } as any 18 | : props?.sideEffect === SideEffect.ZERO_HEIGHT 19 | ? { padding: 0, height: 0, fontSize: 0, border: 0 } 20 | : props?.sideEffect === SideEffect.OUT_OF_BOUNDS 21 | ? { position: 'absolute', left: '10000px' } 22 | : {} 23 | 24 | return
25 | 32 | {props?.sideEffect === SideEffect.OVERLAYING_ELEMENT ? : null} 33 |

34 | Edit src/App.tsx and save to test HMR 35 |

36 |
37 | } 38 | 39 | const Overlay: React.FC = () => { 40 | return
47 | You can't click it! 48 |
49 | } 50 | -------------------------------------------------------------------------------- /framework-comparisons/src/components/Input.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "vitest" 2 | import { render, fireEvent } from '@testing-library/react' 3 | import '@testing-library/jest-dom' 4 | 5 | import { Input, SideEffect } from '../components/Input' 6 | 7 | describe('Input Component', () => { 8 | it('can set value of input without side effect', () => { 9 | const { getByPlaceholderText } = render() 10 | const input = getByPlaceholderText('username') as HTMLInputElement 11 | expect(input.value).toBe('') 12 | fireEvent.change(input, { target: { value: 'Hello World' } }) 13 | expect(input.value).toBe('Hello World') 14 | }) 15 | 16 | it('can not set value of input if input is disabled', () => { 17 | const { getByPlaceholderText } = render() 18 | const input = getByPlaceholderText('username') as HTMLInputElement 19 | expect(input.value).toBe('') 20 | fireEvent.change(input, { target: { value: 'Hello World' } }) 21 | expect(input.value).toBe('Hello World') 22 | }) 23 | 24 | it('can not set value of input if input has zero height', () => { 25 | const { getByPlaceholderText } = render() 26 | const input = getByPlaceholderText('username') as HTMLInputElement 27 | expect(input.value).toBe('') 28 | fireEvent.change(input, { target: { value: 'Hello World' } }) 29 | expect(input.value).toBe('Hello World') 30 | }) 31 | 32 | it('can not set value of input if input is invisible', () => { 33 | const { getByPlaceholderText } = render() 34 | const input = getByPlaceholderText('username') as HTMLInputElement 35 | expect(input.value).toBe('') 36 | fireEvent.change(input, { target: { value: 'Hello World' } }) 37 | expect(input.value).toBe('Hello World') 38 | }) 39 | 40 | it('can not set value of input if input is overlayed by another element', () => { 41 | const { getByPlaceholderText } = render() 42 | const input = getByPlaceholderText('username') as HTMLInputElement 43 | expect(input.value).toBe('') 44 | fireEvent.change(input, { target: { value: 'Hello World' } }) 45 | expect(input.value).toBe('Hello World') 46 | }) 47 | 48 | it('can not set value of input if input is out of bounds', () => { 49 | const { getByPlaceholderText } = render() 50 | const input = getByPlaceholderText('username') as HTMLInputElement 51 | expect(input.value).toBe('') 52 | fireEvent.change(input, { target: { value: 'Hello World' } }) 53 | expect(input.value).toBe('Hello World') 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /framework-comparisons/src/components/Input.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | export enum SideEffect { 4 | DISABLED = 0, 5 | ZERO_HEIGHT, 6 | INVISIBLE, 7 | OVERLAYING_ELEMENT, 8 | OUT_OF_BOUNDS 9 | } 10 | 11 | interface Props { 12 | sideEffect?: SideEffect 13 | } 14 | 15 | export const Input: React.FC = (props?: Props) => { 16 | const inputProps: React.InputHTMLAttributes = props?.sideEffect === SideEffect.DISABLED 17 | ? { disabled: true } 18 | : props?.sideEffect === SideEffect.INVISIBLE 19 | ? { style: { visibility: 'hidden' } } 20 | : props?.sideEffect === SideEffect.ZERO_HEIGHT 21 | ? { style: { padding: 0, height: 0, fontSize: 0, border: 0 } } 22 | : props?.sideEffect === SideEffect.OUT_OF_BOUNDS 23 | ? { style: { position: 'absolute', left: '10000px' } } 24 | : {} 25 | 26 | return
27 | 32 | {props?.sideEffect === SideEffect.OVERLAYING_ELEMENT ? : null} 33 |
34 | } 35 | 36 | const Overlay: React.FC = () => { 37 | return
44 | You can't see it! 45 |
46 | } 47 | -------------------------------------------------------------------------------- /framework-comparisons/src/components/InstantSearch.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | padding: 1em; 4 | } 5 | 6 | .ais-SearchBox { 7 | margin: 1em 0; 8 | } 9 | 10 | .ais-Pagination { 11 | margin-top: 1em; 12 | } 13 | 14 | .left-panel { 15 | float: left; 16 | width: 250px; 17 | } 18 | 19 | .right-panel { 20 | margin-left: 260px; 21 | } 22 | 23 | .ais-InstantSearch { 24 | max-width: 960px; 25 | overflow: hidden; 26 | margin: 0 auto; 27 | } 28 | 29 | .ais-Hits-item { 30 | margin-bottom: 1em; 31 | width: calc(50% - 1rem); 32 | } 33 | 34 | .ais-Hits-item img { 35 | margin-right: 1em; 36 | } 37 | 38 | .hit-name { 39 | margin-bottom: 0.5em; 40 | } 41 | 42 | .hit-description { 43 | color: #888; 44 | font-size: 14px; 45 | margin-bottom: 0.5em; 46 | } 47 | -------------------------------------------------------------------------------- /framework-comparisons/src/components/InstantSearch.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, it, vi } from "vitest" 2 | import { render, waitFor, fireEvent, act } from '@testing-library/react' 3 | import '@testing-library/jest-dom' 4 | 5 | import { InstantSearchComponent } from './InstantSearch' 6 | import fixture from './__fixtures__/algolia.json' 7 | 8 | vi.mock('algoliasearch/lite', () => ({ 9 | default: vi.fn().mockReturnValue({ 10 | search: vi.fn().mockImplementation(async () => { 11 | const fixture = await import('./__fixtures__/algolia.json') 12 | return Promise.resolve(fixture) 13 | }), 14 | searchForFacetValues: vi.fn(), 15 | addAlgoliaAgent: vi.fn(), 16 | clearCache: vi.fn() 17 | }) 18 | })) 19 | 20 | vi.mock('../constants', () => ({ 21 | HEADING: 'mocked out' 22 | })) 23 | 24 | describe('Card Component', () => { 25 | it('test component without making a request', async () => { 26 | const { debug, getByText, queryAllByRole, container } = render() 27 | 28 | // validate it finds items 29 | await waitFor(() => { expect(getByText('7 32GB - Black Onyx (Verizon)')).toBeInTheDocument() }) 30 | 31 | // has correct item at the top 32 | expect(container.querySelector('.ais-Highlight')!.textContent) 33 | .toBe(fixture.results[0].hits[0].name) 34 | expect(container.querySelector('.ais-RefinementList-labelText')!.textContent) 35 | .toBe('Incipio') 36 | 37 | // orders brands, selected first 38 | await act(() => fireEvent.click(getByText('Samsung'))) 39 | expect(container.querySelector('.ais-RefinementList-labelText')!.textContent) 40 | .toBe('Samsung') 41 | 42 | // can reset selection 43 | await act(() => fireEvent.click(queryAllByRole('button')[0])) 44 | expect(container.querySelector('.ais-RefinementList-labelText')!.textContent) 45 | .toBe('Incipio') 46 | }) 47 | 48 | it('can mock out indidivual file imports', () => { 49 | const { getByText } = render() 50 | expect(getByText('mocked out')).toBeInTheDocument() 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /framework-comparisons/src/components/InstantSearch.tsx: -------------------------------------------------------------------------------- 1 | import algoliasearch from 'algoliasearch/lite'; 2 | import React, { Component } from 'react'; 3 | import { 4 | InstantSearch, 5 | Hits, 6 | SearchBox, 7 | Pagination, 8 | Highlight, 9 | ClearRefinements, 10 | RefinementList, 11 | Configure, 12 | } from 'react-instantsearch-dom'; 13 | import type { Hit } from 'react-instantsearch-core'; 14 | 15 | import { HEADING } from '../constants' 16 | import './InstantSearch.css'; 17 | 18 | const searchClient = algoliasearch( 19 | 'appId', 20 | 'appAPIKey' 21 | ); 22 | 23 | export class InstantSearchComponent extends Component { 24 | render() { 25 | return ( 26 |
27 |

{HEADING}

28 | 29 |
30 | 31 |

Brands

32 | 33 | 34 |
35 |
36 | 37 | 38 | 39 |
40 |
41 |
42 | ); 43 | } 44 | } 45 | 46 | function Hit(props: { hit: Hit }) { 47 | return ( 48 |
49 | {props.hit.name} 50 |
51 | 52 |
53 |
54 | 55 |
56 |
${props.hit.price}
57 |
58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /framework-comparisons/src/components/InstantSearch.wdio.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | import { expect, browser, $ } from '@wdio/globals' 3 | import { mock, fn } from '@wdio/browser-runner' 4 | import { render } from '@testing-library/react' 5 | 6 | import { InstantSearchComponent } from './InstantSearch' 7 | import fixture from './__fixtures__/algolia.json' 8 | import './InstantSearch.css' 9 | 10 | mock('algoliasearch/lite', () => ({ 11 | default: fn().mockReturnValue({ 12 | search: fn().mockImplementation(async () => { 13 | const fixture = await import('./__fixtures__/algolia.json') 14 | return Promise.resolve(fixture) 15 | }), 16 | searchForFacetValues: fn(), 17 | addAlgoliaAgent: fn(), 18 | clearCache: fn() 19 | }) 20 | })) 21 | 22 | mock('../constants', () => ({ 23 | HEADING: 'mocked out' 24 | })) 25 | 26 | describe('InstantSearch Component', () => { 27 | it('works without making requests', async () => { 28 | // render component 29 | render() 30 | 31 | // validate it finds items 32 | await browser.waitUntil( 33 | async () => (await $$('.right-panel .ais-Hits-list li')).length === 8 34 | ) 35 | 36 | // has correct item at the top 37 | await expect($('.ais-Highlight')).toHaveText(fixture.results[0].hits[0].name) 38 | await expect($('.ais-RefinementList-item')).toHaveText('Incipio 608') 39 | 40 | // orders brands, selected first 41 | await $('.ais-RefinementList-item=Samsung 292').click() 42 | await expect($('.ais-RefinementList-item')).toHaveText('Samsung 292') 43 | 44 | // can reset selection 45 | await $('.ais-ClearRefinements-button').click() 46 | await expect($('.ais-RefinementList-item')).toHaveText('Incipio 608') 47 | }) 48 | 49 | it('can mock out indidivual file imports', async () => { 50 | const { container } = render() 51 | await expect($(container).$('h1')).toHaveText('mocked out') 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /framework-comparisons/src/components/Zoom.cy.tsx: -------------------------------------------------------------------------------- 1 | import { Zoom } from './Zoom'; 2 | 3 | describe('Zoom component', () => { 4 | it.skip('can zoom in/out via scrolling', () => { 5 | cy.mount() 6 | 7 | /** 8 | * without `ensureScrollable: false` the command fails due to: 9 | * -> failed because this element is not scrollable 10 | */ 11 | cy.get('img').scrollTo(0, -200, { ensureScrollable: false }) 12 | 13 | /** 14 | * assertion fails, no scrolling emitted on the element 15 | */ 16 | cy.get('.react-transform-component') 17 | .invoke('attr', 'style') 18 | .should('eq', 'transform: translate(-16px, -16px) scale(1.16)') 19 | }) 20 | 21 | it('attempt to scroll via "cypress-real-events"', () => { 22 | cy.mount() 23 | cy.get('img').realMouseDown() 24 | /** 25 | * TypeError: cy.get(...).realMouseWheel is not a function 26 | */ 27 | cy.get('img').realMouseWheel({ deltaY: -100 }) 28 | cy.get('.react-transform-component') 29 | .invoke('attr', 'style') 30 | .should('eq', 'transform: translate(-16px, -16px) scale(1.16)') 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /framework-comparisons/src/components/Zoom.pw.tsx: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/experimental-ct-react' 2 | import { Zoom } from './Zoom' 3 | 4 | test.use({ viewport: { width: 500, height: 500 } }) 5 | 6 | test('Zoom Component: can zoom in/out via scrolling', async ({ mount, page }) => { 7 | const component = await mount() 8 | 9 | await component.locator('.react-transform-component').hover() 10 | await page.mouse.wheel(0, -200) 11 | 12 | const style = await component.locator('.react-transform-component').getAttribute('style') 13 | await expect(style).toBe('transform: translate(-16px, -16.06px) scale(1.16);') 14 | }) 15 | 16 | test('Zoom Component: can zoom in via gestures', () => { 17 | /** 18 | * see https://github.com/microsoft/playwright/issues/2903 19 | */ 20 | throw new Error('Not supported') 21 | }) 22 | -------------------------------------------------------------------------------- /framework-comparisons/src/components/Zoom.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'; 4 | 5 | export class Zoom extends Component { 6 | render() { 7 | return ( 8 | 9 | 10 | test 11 | 12 | 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /framework-comparisons/src/components/Zoom.wdio.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | import { expect, browser, $ } from '@wdio/globals' 3 | import { logDOM, render } from '@testing-library/react' 4 | 5 | import { Zoom } from '../components/Zoom' 6 | import '../App.css' 7 | 8 | describe('Zoom Component', () => { 9 | it('can zoom in/out via scrolling', async () => { 10 | const { container } = render() 11 | const $container = await $(container) 12 | 13 | // zoom in 14 | await browser.action('wheel').scroll({ origin: $container.$('img'), deltaY: -250 }).perform() 15 | await expect($container.$('.react-transform-component')) 16 | .toHaveAttr('style', expect.stringContaining('transform: translate(-16px, -16px) scale(1.16)')) 17 | 18 | // zoom out 19 | await browser.action('wheel').scroll({ origin: $container.$('img'), deltaY: 250 }).perform() 20 | await expect($container.$('.react-transform-component')) 21 | .toHaveAttr('style', expect.stringContaining('transform: translate(0px, 0px) scale(1)')) 22 | }) 23 | 24 | it('can zoom in via gestures', async () => { 25 | const { container } = render() 26 | const $container = await $(container) 27 | 28 | await browser.pause(5000) 29 | // zoom in 30 | await browser.actions([ 31 | browser.action('pointer') 32 | .move({ origin: await $container.$('img'), x: 0, y: 0 }) 33 | .down() 34 | .move({ x: -100, y: -100 }) 35 | .up(), 36 | 37 | browser.action('pointer') 38 | .move({ origin: await $container.$('img'), x: 0, y: 0 }) 39 | .down() 40 | .move({ x: 100, y: 100 }) 41 | .up() 42 | ]) 43 | await browser.pause(5000) 44 | await expect($container.$('.react-transform-component')) 45 | .toHaveAttr('style', expect.stringContaining('transform: translate(-16px, -16px) scale(1.16)')) 46 | 47 | // zoom out 48 | await browser.actions([ 49 | browser.action('pointer') 50 | .move({ x: -100, y: -100 }) 51 | .down() 52 | .move({ origin: await $container.$('img'), x: 0, y: 0 }) 53 | .up(), 54 | 55 | browser.action('pointer') 56 | .move({ x: 100, y: 100 }) 57 | .down() 58 | .move({ origin: await $container.$('img'), x: 0, y: 0 }) 59 | .up() 60 | ]) 61 | await expect($container.$('.react-transform-component')) 62 | .toHaveAttr('style', expect.stringContaining('transform: translate(0px, 0px) scale(1)')) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /framework-comparisons/src/components/input.pw.tsx: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/experimental-ct-react' 2 | import { Input, SideEffect } from './Input' 3 | 4 | test.use({ viewport: { width: 500, height: 500 } }) 5 | 6 | test('Input Component: can be clicked without a side effect', async ({ mount }) => { 7 | const component = await mount() 8 | const input = await component.getByPlaceholder('username') 9 | await expect(input).toHaveValue('') 10 | await input.fill('Hello World!') 11 | await expect(input).toHaveValue('Hello World!') 12 | }) 13 | 14 | test('Input Component: can not set value of input if input is disabled', async ({ mount }) => { 15 | const component = await mount() 16 | const input = await component.getByPlaceholder('username') 17 | await expect(input).toHaveValue('') 18 | /** 19 | * test times out with log: 20 | * waiting for element to be visible, enabled and editable 21 | */ 22 | await input.fill('Hello World!') 23 | await expect(input).toHaveValue('Hello World!') 24 | }) 25 | 26 | test('Input Component: can not set value of input if input has zero height', async ({ mount }) => { 27 | const component = await mount() 28 | const input = await component.getByPlaceholder('username') 29 | await expect(input).toHaveValue('') 30 | /** 31 | * test times out with log: 32 | * waiting for element to be visible, enabled and editable 33 | */ 34 | await input.fill('Hello World!') 35 | await expect(input).toHaveValue('Hello World!') 36 | }) 37 | 38 | test('Input Component: can not set value of input if input is invisible', async ({ mount }) => { 39 | const component = await mount() 40 | const input = await component.getByPlaceholder('username') 41 | await expect(input).toHaveValue('') 42 | /** 43 | * test times out with log: 44 | * waiting for element to be visible, enabled and editable 45 | */ 46 | await input.fill('Hello World!') 47 | await expect(input).toHaveValue('Hello World!') 48 | }) 49 | 50 | test('Input Component: can set value of input even though input is overlayed by another element', async ({ mount }) => { 51 | const component = await mount() 52 | const input = await component.getByPlaceholder('username') 53 | await expect(input).toHaveValue('') 54 | await input.fill('Hello World!') 55 | await expect(input).toHaveValue('Hello World!') 56 | }) 57 | 58 | test('Input Component: can be set value even though input is out of bounds', async ({ mount }) => { 59 | const component = await mount() 60 | const input = await component.getByPlaceholder('username') 61 | await expect(input).toHaveValue('') 62 | await input.fill('Hello World!') 63 | await expect(input).toHaveValue('Hello World!') 64 | }) 65 | -------------------------------------------------------------------------------- /framework-comparisons/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const HEADING = 'React InstantSearch e-commerce demo' 2 | -------------------------------------------------------------------------------- /framework-comparisons/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-text-size-adjust: 100%; 15 | } 16 | 17 | a { 18 | font-weight: 500; 19 | color: #646cff; 20 | text-decoration: inherit; 21 | } 22 | a:hover { 23 | color: #535bf2; 24 | } 25 | 26 | body { 27 | margin: 0; 28 | display: flex; 29 | place-items: center; 30 | min-width: 320px; 31 | min-height: 100vh; 32 | } 33 | 34 | h1 { 35 | font-size: 3.2em; 36 | line-height: 1.1; 37 | } 38 | 39 | button { 40 | border-radius: 8px; 41 | border: 1px solid transparent; 42 | padding: 0.6em 1.2em; 43 | font-size: 1em; 44 | font-weight: 500; 45 | font-family: inherit; 46 | background-color: #1a1a1a; 47 | cursor: pointer; 48 | transition: border-color 0.25s; 49 | } 50 | button:hover { 51 | border-color: #646cff; 52 | } 53 | button:focus, 54 | button:focus-visible { 55 | outline: 4px auto -webkit-focus-ring-color; 56 | } 57 | 58 | @media (prefers-color-scheme: light) { 59 | :root { 60 | color: #213547; 61 | background-color: #ffffff; 62 | } 63 | a:hover { 64 | color: #747bff; 65 | } 66 | button { 67 | background-color: #f9f9f9; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /framework-comparisons/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /framework-comparisons/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /framework-comparisons/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "ignoreDeprecations": "5.0" 19 | }, 20 | "include": ["src"], 21 | "references": [{ "path": "./tsconfig.node.json" }] 22 | } 23 | -------------------------------------------------------------------------------- /framework-comparisons/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /framework-comparisons/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /framework-comparisons/vitest.conf.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import { defineConfig } from 'vite' 5 | import react from '@vitejs/plugin-react' 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | plugins: [react()], 10 | test: { 11 | globals: true, 12 | environment: 'jsdom', 13 | coverage: { 14 | reporter: ['text', 'json', 'html'], 15 | } 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /ionic-react/.browserslistrc: -------------------------------------------------------------------------------- 1 | Chrome >=79 2 | ChromeAndroid >=79 3 | Firefox >=70 4 | Edge >=79 5 | Safari >=14 6 | iOS >=14 -------------------------------------------------------------------------------- /ionic-react/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:react/recommended', 8 | 'eslint:recommended' 9 | ], 10 | parserOptions: { 11 | ecmaVersion: 2020 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ionic-react/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /dist 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | /.vscode/* 21 | !/.vscode/extensions.json 22 | .idea 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # Optional eslint cache 29 | .eslintcache 30 | -------------------------------------------------------------------------------- /ionic-react/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ionic.ionic" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /ionic-react/capacitor.config.ts: -------------------------------------------------------------------------------- 1 | import { CapacitorConfig } from '@capacitor/cli'; 2 | 3 | const config: CapacitorConfig = { 4 | appId: 'io.ionic.starter', 5 | appName: 'ionic-react', 6 | webDir: 'dist', 7 | server: { 8 | androidScheme: 'https' 9 | } 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /ionic-react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ionic App 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /ionic-react/ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-react", 3 | "integrations": { 4 | "capacitor": {} 5 | }, 6 | "type": "react-vite" 7 | } 8 | -------------------------------------------------------------------------------- /ionic-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-react", 3 | "private": true, 4 | "version": "0.0.1", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview", 10 | "test.unit": "vitest", 11 | "wdio": "wdio run ./wdio.conf.ts" 12 | }, 13 | "dependencies": { 14 | "@capacitor/app": "5.0.7", 15 | "@capacitor/core": "5.7.1", 16 | "@capacitor/haptics": "5.0.7", 17 | "@capacitor/keyboard": "5.0.8", 18 | "@capacitor/status-bar": "5.0.7", 19 | "@ionic/react": "^7.7.3", 20 | "@ionic/react-router": "^7.7.3", 21 | "@types/react-router": "^5.1.20", 22 | "@types/react-router-dom": "^5.3.3", 23 | "ionicons": "^7.2.2", 24 | "react-dom": "^18.2.0", 25 | "react-router": "^5.3.4", 26 | "react-router-dom": "^5.3.4" 27 | }, 28 | "devDependencies": { 29 | "@capacitor/cli": "5.7.1", 30 | "@testing-library/dom": ">=9.3.4", 31 | "@testing-library/jest-dom": "^6.4.2", 32 | "@testing-library/react": "^14.2.1", 33 | "@testing-library/user-event": "^14.5.2", 34 | "@types/react": "^18.2.61", 35 | "@types/react-dom": "^18.2.19", 36 | "@vitejs/plugin-legacy": "^5.3.1", 37 | "@vitejs/plugin-react": "^4.2.1", 38 | "@wdio/browser-runner": "^8.32.3", 39 | "@wdio/cli": "^8.32.3", 40 | "@wdio/mocha-framework": "^8.32.3", 41 | "@wdio/spec-reporter": "^8.32.2", 42 | "react": "^18.2.0", 43 | "terser": "^5.28.1", 44 | "ts-node": "^10.9.2", 45 | "typescript": "^5.3.3", 46 | "vite": "^5.1.4" 47 | }, 48 | "description": "An Ionic project" 49 | } 50 | -------------------------------------------------------------------------------- /ionic-react/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdriverio/component-testing-examples/9355c75f233954e50eaba77ba4a1c882c25c1c21/ionic-react/public/favicon.png -------------------------------------------------------------------------------- /ionic-react/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Ionic App", 3 | "name": "My Ionic App", 4 | "icons": [ 5 | { 6 | "src": "assets/icon/favicon.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "assets/icon/icon.png", 12 | "type": "image/png", 13 | "sizes": "512x512", 14 | "purpose": "maskable" 15 | } 16 | ], 17 | "start_url": ".", 18 | "display": "standalone", 19 | "theme_color": "#ffffff", 20 | "background_color": "#ffffff" 21 | } 22 | -------------------------------------------------------------------------------- /ionic-react/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdriverio/component-testing-examples/9355c75f233954e50eaba77ba4a1c882c25c1c21/ionic-react/resources/icon.png -------------------------------------------------------------------------------- /ionic-react/resources/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdriverio/component-testing-examples/9355c75f233954e50eaba77ba4a1c882c25c1c21/ionic-react/resources/splash.png -------------------------------------------------------------------------------- /ionic-react/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { expect } from '@wdio/globals' 3 | import { render } from '@testing-library/react'; 4 | import App from './App'; 5 | 6 | async function getVisiblePage () { 7 | return $$('>>>.container').find((page) => page.isDisplayed()); 8 | } 9 | 10 | describe('App', () => { 11 | it('renders without crashing', async () => { 12 | const { baseElement } = render(); 13 | expect(baseElement).toBeDefined(); 14 | }); 15 | 16 | it('should be able to navigate to other tabs', async () => { 17 | const { baseElement } = render(); 18 | const navLinks = await $(baseElement).$$('>>>ion-label'); 19 | await navLinks[0].click(); 20 | 21 | await expect(await getVisiblePage()).toHaveText( 22 | expect.stringContaining('Tab 1 page')); 23 | 24 | await navLinks[1].click(); 25 | await expect(await getVisiblePage()).toHaveText( 26 | expect.stringContaining('Tab 2 page')); 27 | 28 | await navLinks[2].click(); 29 | await expect(await getVisiblePage()).toHaveText( 30 | expect.stringContaining('Tab 3 page')); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /ionic-react/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Redirect, Route } from 'react-router-dom'; 2 | import { 3 | IonApp, 4 | IonIcon, 5 | IonLabel, 6 | IonRouterOutlet, 7 | IonTabBar, 8 | IonTabButton, 9 | IonTabs, 10 | setupIonicReact 11 | } from '@ionic/react'; 12 | import { IonReactRouter } from '@ionic/react-router'; 13 | import { ellipse, square, triangle } from 'ionicons/icons'; 14 | import Tab1 from './pages/Tab1'; 15 | import Tab2 from './pages/Tab2'; 16 | import Tab3 from './pages/Tab3'; 17 | 18 | /* Core CSS required for Ionic components to work properly */ 19 | import '@ionic/react/css/core.css'; 20 | 21 | /* Basic CSS for apps built with Ionic */ 22 | import '@ionic/react/css/normalize.css'; 23 | import '@ionic/react/css/structure.css'; 24 | import '@ionic/react/css/typography.css'; 25 | 26 | /* Optional CSS utils that can be commented out */ 27 | import '@ionic/react/css/padding.css'; 28 | import '@ionic/react/css/float-elements.css'; 29 | import '@ionic/react/css/text-alignment.css'; 30 | import '@ionic/react/css/text-transformation.css'; 31 | import '@ionic/react/css/flex-utils.css'; 32 | import '@ionic/react/css/display.css'; 33 | 34 | /* Theme variables */ 35 | import './theme/variables.css'; 36 | 37 | setupIonicReact(); 38 | 39 | const App: React.FC = () => ( 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 62 | 63 | 66 | 67 | 70 | 71 | 72 | 73 | 74 | ); 75 | 76 | export default App; 77 | -------------------------------------------------------------------------------- /ionic-react/src/components/ExploreContainer.css: -------------------------------------------------------------------------------- 1 | .container { 2 | text-align: center; 3 | position: absolute; 4 | left: 0; 5 | right: 0; 6 | top: 50%; 7 | transform: translateY(-50%); 8 | } 9 | 10 | .container strong { 11 | font-size: 20px; 12 | line-height: 26px; 13 | } 14 | 15 | .container p { 16 | font-size: 16px; 17 | line-height: 22px; 18 | color: #8c8c8c; 19 | margin: 0; 20 | } 21 | 22 | .container a { 23 | text-decoration: none; 24 | } -------------------------------------------------------------------------------- /ionic-react/src/components/ExploreContainer.tsx: -------------------------------------------------------------------------------- 1 | import './ExploreContainer.css'; 2 | 3 | interface ContainerProps { 4 | name: string; 5 | } 6 | 7 | const ExploreContainer: React.FC = ({ name }) => { 8 | return ( 9 |
10 | {name} 11 |

Explore UI Components

12 |
13 | ); 14 | }; 15 | 16 | export default ExploreContainer; 17 | -------------------------------------------------------------------------------- /ionic-react/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import App from './App'; 4 | 5 | const container = document.getElementById('root'); 6 | const root = createRoot(container!); 7 | root.render( 8 | 9 | 10 | 11 | ); -------------------------------------------------------------------------------- /ionic-react/src/pages/Tab1.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdriverio/component-testing-examples/9355c75f233954e50eaba77ba4a1c882c25c1c21/ionic-react/src/pages/Tab1.css -------------------------------------------------------------------------------- /ionic-react/src/pages/Tab1.tsx: -------------------------------------------------------------------------------- 1 | import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react'; 2 | import ExploreContainer from '../components/ExploreContainer'; 3 | import './Tab1.css'; 4 | 5 | const Tab1: React.FC = () => { 6 | return ( 7 | 8 | 9 | 10 | Tab 1 11 | 12 | 13 | 14 | 15 | 16 | Tab 1 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default Tab1; 26 | -------------------------------------------------------------------------------- /ionic-react/src/pages/Tab2.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdriverio/component-testing-examples/9355c75f233954e50eaba77ba4a1c882c25c1c21/ionic-react/src/pages/Tab2.css -------------------------------------------------------------------------------- /ionic-react/src/pages/Tab2.tsx: -------------------------------------------------------------------------------- 1 | import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react'; 2 | import ExploreContainer from '../components/ExploreContainer'; 3 | import './Tab2.css'; 4 | 5 | const Tab2: React.FC = () => { 6 | return ( 7 | 8 | 9 | 10 | Tab 2 11 | 12 | 13 | 14 | 15 | 16 | Tab 2 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default Tab2; 26 | -------------------------------------------------------------------------------- /ionic-react/src/pages/Tab3.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdriverio/component-testing-examples/9355c75f233954e50eaba77ba4a1c882c25c1c21/ionic-react/src/pages/Tab3.css -------------------------------------------------------------------------------- /ionic-react/src/pages/Tab3.tsx: -------------------------------------------------------------------------------- 1 | import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react'; 2 | import ExploreContainer from '../components/ExploreContainer'; 3 | import './Tab3.css'; 4 | 5 | const Tab3: React.FC = () => { 6 | return ( 7 | 8 | 9 | 10 | Tab 3 11 | 12 | 13 | 14 | 15 | 16 | Tab 3 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default Tab3; 26 | -------------------------------------------------------------------------------- /ionic-react/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /ionic-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /ionic-react/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /ionic-react/vite.config.ts: -------------------------------------------------------------------------------- 1 | import legacy from '@vitejs/plugin-legacy' 2 | import react from '@vitejs/plugin-react' 3 | import { defineConfig } from 'vite' 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [ 8 | react(), 9 | legacy() 10 | ], 11 | test: { 12 | globals: true, 13 | environment: 'jsdom', 14 | setupFiles: './src/setupTests.ts', 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /lit-typescript-vite/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /lit-typescript-vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + Lit + TS 8 | 9 | 10 | 11 | 12 | 13 |

Vite + Lit

14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /lit-typescript-vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lit-typescript-vite", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "main": "dist/my-element.es.js", 7 | "exports": { 8 | ".": "./dist/my-element.es.js" 9 | }, 10 | "types": "types/my-element.d.ts", 11 | "files": [ 12 | "dist", 13 | "types" 14 | ], 15 | "scripts": { 16 | "dev": "vite", 17 | "build": "tsc && vite build", 18 | "wdio": "wdio run ./wdio.conf.ts" 19 | }, 20 | "dependencies": { 21 | "lit": "^3.2.1" 22 | }, 23 | "devDependencies": { 24 | "@testing-library/dom": "^10.4.0", 25 | "@types/mocha": "^10.0.10", 26 | "@wdio/browser-runner": "^9.7.3", 27 | "@wdio/cli": "^9.7.3", 28 | "@wdio/mocha-framework": "^9.7.3", 29 | "@wdio/spec-reporter": "^9.6.3", 30 | "ts-node": "^10.9.2", 31 | "typescript": "^5.7.3", 32 | "vite": "^6.1.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lit-typescript-vite/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lit-typescript-vite/src/assets/lit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lit-typescript-vite/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color-scheme: light dark; 8 | color: rgba(255, 255, 255, 0.87); 9 | background-color: #242424; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | a { 19 | font-weight: 500; 20 | color: #646cff; 21 | text-decoration: inherit; 22 | } 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | body { 28 | margin: 0; 29 | display: flex; 30 | place-items: center; 31 | min-width: 320px; 32 | min-height: 100vh; 33 | } 34 | 35 | @media (prefers-color-scheme: light) { 36 | :root { 37 | color: #213547; 38 | background-color: #ffffff; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lit-typescript-vite/src/tests/my-element.test.ts: -------------------------------------------------------------------------------- 1 | import { html, render } from 'lit' 2 | import { $, expect } from '@wdio/globals' 3 | 4 | import '../my-element' 5 | 6 | describe('Lit component testing', () => { 7 | it('should increment value on click', async () => { 8 | render( 9 | html``, 10 | document.body 11 | ) 12 | 13 | const button = await $('my-element').$('>>>button') 14 | await expect(button).toHaveText('count is 0') 15 | 16 | await button.click() 17 | await button.click() 18 | 19 | await expect(button).toHaveText('count is 2') 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /lit-typescript-vite/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /lit-typescript-vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "outDir": "./types", 8 | "strict": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "moduleResolution": "Node", 14 | "isolatedModules": true, 15 | "allowSyntheticDefaultImports": true, 16 | "experimentalDecorators": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "useDefineForClassFields": false, 19 | "skipLibCheck": true 20 | }, 21 | "include": ["src/**/*.ts"], 22 | "references": [{ "path": "./tsconfig.node.json" }] 23 | } 24 | -------------------------------------------------------------------------------- /lit-typescript-vite/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /lit-typescript-vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | 3 | // https://vitejs.dev/config/ 4 | export default defineConfig({ 5 | build: { 6 | lib: { 7 | entry: 'src/my-element.ts', 8 | formats: ['es'] 9 | }, 10 | rollupOptions: { 11 | external: /^lit/ 12 | } 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /nextjs/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /nextjs/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /nextjs/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "wdio": "wdio run ./wdio.conf.mts" 10 | }, 11 | "dependencies": { 12 | "react": "^19.0.0", 13 | "react-dom": "^19.0.0", 14 | "next": "15.1.6" 15 | }, 16 | "devDependencies": { 17 | "@tailwindcss/postcss": "^4.0.4", 18 | "@testing-library/jest-dom": "^6.6.3", 19 | "@testing-library/react": "^16.2.0", 20 | "@types/node": "^22", 21 | "@types/react": "^19", 22 | "@types/react-dom": "^19", 23 | "@vitejs/plugin-react": "^4.3.4", 24 | "@wdio/browser-runner": "^9.7.3", 25 | "@wdio/cli": "^9.7.3", 26 | "@wdio/mocha-framework": "^9.7.3", 27 | "@wdio/spec-reporter": "^9.6.3", 28 | "autoprefixer": "^10.4.20", 29 | "postcss": "^8.5.1", 30 | "react": "^19.0.0", 31 | "tailwindcss": "^4.0.4", 32 | "ts-node": "^10.9.2", 33 | "typescript": "^5", 34 | "eslint": "^9", 35 | "eslint-config-next": "15.1.6", 36 | "@eslint/eslintrc": "^3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /nextjs/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | "@tailwindcss/postcss": {}, 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /nextjs/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nextjs/public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nextjs/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nextjs/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nextjs/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nextjs/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdriverio/component-testing-examples/9355c75f233954e50eaba77ba4a1c882c25c1c21/nextjs/src/app/favicon.ico -------------------------------------------------------------------------------- /nextjs/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --background: #ffffff; 7 | --foreground: #171717; 8 | } 9 | 10 | @media (prefers-color-scheme: dark) { 11 | :root { 12 | --background: #0a0a0a; 13 | --foreground: #ededed; 14 | } 15 | } 16 | 17 | body { 18 | color: var(--foreground); 19 | background: var(--background); 20 | font-family: Arial, Helvetica, sans-serif; 21 | } 22 | -------------------------------------------------------------------------------- /nextjs/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Geist, Geist_Mono } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const geistSans = Geist({ 6 | variable: "--font-geist-sans", 7 | subsets: ["latin"], 8 | }); 9 | 10 | const geistMono = Geist_Mono({ 11 | variable: "--font-geist-mono", 12 | subsets: ["latin"], 13 | }); 14 | 15 | export const metadata: Metadata = { 16 | title: "Create Next App", 17 | description: "Generated by create next app", 18 | }; 19 | 20 | export default function RootLayout({ 21 | children, 22 | }: Readonly<{ 23 | children: React.ReactNode; 24 | }>) { 25 | return ( 26 | 27 | 30 | {children} 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /nextjs/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | content: [ 5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | colors: { 12 | background: "var(--background)", 13 | foreground: "var(--foreground)", 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | } satisfies Config; 19 | -------------------------------------------------------------------------------- /nextjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /nuxt/.env.example: -------------------------------------------------------------------------------- 1 | SUPABASE_URL="http://localhost:3000" 2 | SUPABASE_KEY="" 3 | SUPABASE_SERVICE_KEY="" -------------------------------------------------------------------------------- /nuxt/.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .nuxt 4 | .nitro 5 | .cache 6 | dist 7 | 8 | # Node dependencies 9 | node_modules 10 | 11 | # Logs 12 | logs 13 | *.log 14 | 15 | # Misc 16 | .DS_Store 17 | .fleet 18 | .idea 19 | 20 | # Local env files 21 | .env 22 | .env.* 23 | !.env.example 24 | -------------------------------------------------------------------------------- /nuxt/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /nuxt/README.md: -------------------------------------------------------------------------------- 1 | # How to Get Started 2 | 3 | 1. Ensure you have [docker installed](https://docs.docker.com/get-docker/) and running 4 | 2. Clone the repo 5 | 6 | ``` 7 | git clone git@github.com:vueschool/forge-4-poc.git 8 | ``` 9 | 10 | 3. Install the dependencies 11 | 12 | ``` 13 | yarn 14 | ``` 15 | 16 | 4. Start the Supabase service 17 | 18 | ``` 19 | yarn supabase:start 20 | ``` 21 | 22 | 5. The needed supabase environment variables will print after the service has started. Add them to your .env file (can use .env.example as a template). You can also retrieve these at any time by running the following: 23 | 24 | ``` 25 | npx supabase status 26 | ``` 27 | 28 | 6. Migrate and seed your database with initial schema and values by running: 29 | 30 | ``` 31 | yarn db:reset 32 | ``` 33 | 34 | 7. Start the dev server 35 | 36 | ``` 37 | yarn dev 38 | ``` 39 | 40 | 8. That's it! 🎉 You're ready to go. 41 | -------------------------------------------------------------------------------- /nuxt/app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | appName: "✨ DecentralSpark", 3 | }); 4 | -------------------------------------------------------------------------------- /nuxt/app.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /nuxt/assets/css/main.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /nuxt/components/AppAlerts.vue: -------------------------------------------------------------------------------- 1 | 4 | 34 | 35 | 58 | -------------------------------------------------------------------------------- /nuxt/components/AppFileUpload.vue: -------------------------------------------------------------------------------- 1 | 38 | 54 | -------------------------------------------------------------------------------- /nuxt/components/Date.test.ts: -------------------------------------------------------------------------------- 1 | import { $, expect } from '@wdio/globals' 2 | import { render } from '@testing-library/vue' 3 | 4 | import DateComponent from './Date.vue' 5 | 6 | describe('Date Component', () => { 7 | it.only('should display date correctly', async () => { 8 | const date = new Date('2043') 9 | const localTime = date.toLocaleString("en-US", { 10 | timeZone: "America/New_York" 11 | }) 12 | render(DateComponent, { props: { date: localTime } }) 13 | const component = await $('span') 14 | await expect(component).toBePresent() 15 | await expect(component).toHaveText( 16 | process.env.CI 17 | ? '12/31/2042, 7:00:00 PM' 18 | : '31/12/2042, 19:00:00' 19 | ) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /nuxt/components/Date.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /nuxt/components/FormField.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 39 | 92 | -------------------------------------------------------------------------------- /nuxt/components/Money.test.ts: -------------------------------------------------------------------------------- 1 | import { $, expect } from '@wdio/globals' 2 | import { render } from '@testing-library/vue' 3 | 4 | import MoneyComponent from './Money.vue' 5 | 6 | interface MoneyProps { 7 | currency: string 8 | decimals: number 9 | amount: number 10 | short: boolean 11 | } 12 | 13 | const testData = new Map() 14 | .set('$12346m', { 15 | currency: 'EUR', 16 | decimals: 10, 17 | amount: 1234567.89, 18 | short: true 19 | }) 20 | .set('€1,234,567.8900000000', { 21 | currency: 'EUR', 22 | decimals: 10, 23 | amount: 1234567.89, 24 | short: false 25 | }) 26 | 27 | describe('Money Component', () => { 28 | for (const [expectedValue, props] of testData.entries()) { 29 | it(`should display money value correctly for ${props.currency}, short: ${props.short}`, async () => { 30 | render(MoneyComponent, { props }) 31 | const component = await $('span') 32 | await expect(component).toBePresent() 33 | await expect(component).toHaveText(expectedValue) 34 | }) 35 | } 36 | }) -------------------------------------------------------------------------------- /nuxt/components/Money.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 41 | -------------------------------------------------------------------------------- /nuxt/components/NavBar.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 69 | -------------------------------------------------------------------------------- /nuxt/components/Pagination.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 56 | -------------------------------------------------------------------------------- /nuxt/components/ProjectCard.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 78 | -------------------------------------------------------------------------------- /nuxt/components/ProjectPledgeForm.vue: -------------------------------------------------------------------------------- 1 | 11 | 28 | -------------------------------------------------------------------------------- /nuxt/components/ProjectsList.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 27 | -------------------------------------------------------------------------------- /nuxt/components/TimeAgo.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 18 | -------------------------------------------------------------------------------- /nuxt/composables/useAlerts.ts: -------------------------------------------------------------------------------- 1 | import { nanoid } from "nanoid"; 2 | interface AlertOptions { 3 | type?: "success" | "error" | "info" | "warning"; 4 | title?: string; 5 | dismissiable?: boolean; 6 | timeout?: number; 7 | } 8 | interface Alert extends AlertOptions { 9 | message: string; 10 | id: string; 11 | } 12 | 13 | export const useAlerts = () => { 14 | const alerts = useState("appAlerts", () => []); 15 | function alert(message: string, options: AlertOptions) { 16 | const id = nanoid(); 17 | const defaults: Partial = { 18 | type: "info", 19 | dismissiable: true, 20 | timeout: 5000, 21 | }; 22 | alerts.value.push({ id, ...defaults, message, ...options }); 23 | 24 | let timeout = 25 | options.timeout === undefined ? defaults.timeout : options.timeout; 26 | if (timeout) { 27 | setTimeout(() => dismiss(id), timeout); 28 | } 29 | } 30 | 31 | function dismiss(idOrAlert: string | Alert) { 32 | const id = typeof idOrAlert === "string" ? idOrAlert : idOrAlert.id; 33 | alerts.value = alerts.value.filter((alert) => alert.id !== id); 34 | } 35 | 36 | function success(message: string, options: AlertOptions = {}) { 37 | alert(message, { ...options, type: "success" }); 38 | } 39 | 40 | function error(message: string, options: AlertOptions = {}) { 41 | alert(message, { ...options, type: "error" }); 42 | } 43 | 44 | function info(message: string, options: AlertOptions = {}) { 45 | alert(message, { ...options, type: "info" }); 46 | } 47 | 48 | function warning(message: string, options: AlertOptions = {}) { 49 | alert(message, { ...options, type: "warning" }); 50 | } 51 | 52 | return { 53 | success, 54 | info, 55 | warning, 56 | error, 57 | alerts, 58 | dismiss, 59 | }; 60 | }; 61 | -------------------------------------------------------------------------------- /nuxt/composables/useCategories.ts: -------------------------------------------------------------------------------- 1 | import { PaginationT, CategoryT, UuidT, ProjectT } from "~/types"; 2 | 3 | export const useCategories = () => { 4 | const supabase = useTypedSupabaseClient(); 5 | const category = ref< 6 | { 7 | projects: ProjectT[]; 8 | } & CategoryT 9 | >(); 10 | const categories = ref([]); 11 | const pagination = ref(); 12 | 13 | const fetchAll = async ({ page }: { page: number } = { page: 1 }) => { 14 | const { getPaginationObject, getRangeEnd, getRangeStart } = usePaginator({ 15 | limit: 10, 16 | page, 17 | }); 18 | 19 | let { data, error, count } = await supabase 20 | .from("categories") 21 | .select("*", { count: "exact" }) 22 | .range(getRangeStart(), getRangeEnd()); 23 | 24 | if (error || !data) { 25 | throw new Error(error?.message || "Error fetching projects"); 26 | } 27 | 28 | categories.value = data; 29 | 30 | if (!count) throw new Error("Count not fetched from Supabase"); 31 | pagination.value = getPaginationObject(count); 32 | }; 33 | 34 | const fetchOne = async ({ uuid }: { uuid: UuidT }) => { 35 | const { data, error } = await supabase 36 | .from("categories") 37 | .select("*, projects(*)") 38 | .eq("uuid", uuid) 39 | .single(); 40 | if (error || !data) 41 | throw new Error(error?.message || "Error fetching category"); 42 | category.value = data; 43 | }; 44 | 45 | return { 46 | item: category, 47 | list: categories, 48 | pagination, 49 | 50 | fetchAll, 51 | fetchOne, 52 | }; 53 | }; 54 | -------------------------------------------------------------------------------- /nuxt/composables/useFilePreview.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeRefOrGetter } from '@vueuse/core' 2 | import { toRef } from '@vueuse/core' 3 | 4 | const previewMap = new WeakMap>() 5 | 6 | export const useFilePreview = ( 7 | _file: MaybeRefOrGetter 8 | ) => { 9 | const fileReference = toRef(_file) 10 | 11 | return computed(() => { 12 | const file = fileReference.value 13 | if (!file) return '' 14 | if (previewMap.has(file)) return previewMap.get(file)?.value 15 | 16 | const reader = new FileReader() 17 | const source = ref('') 18 | 19 | const listener = () => { 20 | source.value = reader.result?.toString() ?? '' 21 | reader.removeEventListener('load', listener) 22 | } 23 | reader.addEventListener('load', listener) 24 | reader.readAsDataURL(file) 25 | previewMap.set(file, source) 26 | 27 | return previewMap.get(file)?.value 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /nuxt/composables/usePaginator.ts: -------------------------------------------------------------------------------- 1 | export const usePaginator = ({ 2 | limit = 8, 3 | page = 1, 4 | }: { 5 | limit?: number; 6 | page?: number; 7 | } = {}) => { 8 | function getRangeStart() { 9 | return (page - 1) * limit; 10 | } 11 | 12 | function getRangeEnd() { 13 | return page * limit - 1; 14 | } 15 | 16 | function getPaginationObject(total: number) { 17 | const totalPages = Math.ceil(total / limit); 18 | const isNextAvailable = totalPages > page; 19 | const isPrevAvailable = page !== 1; 20 | 21 | return { 22 | total: total, 23 | page, 24 | limit, 25 | pages: totalPages, 26 | isNextAvailable, 27 | isPrevAvailable, 28 | next: isNextAvailable ? page + 1 : null, 29 | prev: isPrevAvailable ? page - 1 : null, 30 | }; 31 | } 32 | 33 | return { getRangeStart, getRangeEnd, getPaginationObject }; 34 | }; 35 | -------------------------------------------------------------------------------- /nuxt/composables/useProjects.ts: -------------------------------------------------------------------------------- 1 | import { PaginationT, ProjectT, UuidT } from "~/types"; 2 | 3 | export const useProjects = () => { 4 | const supabase = useTypedSupabaseClient(); 5 | const project = ref({}); 6 | const projects = ref([]); 7 | const pagination = ref({}); 8 | 9 | const fetchAll = async ({ page }: { page: number } = { page: 1 }) => { 10 | const { getPaginationObject, getRangeEnd, getRangeStart } = usePaginator({ 11 | page, 12 | }); 13 | 14 | let { data, error, count } = await supabase 15 | .from("projects") 16 | .select("*, categories(*)", { count: "exact" }) 17 | .range(getRangeStart(), getRangeEnd()); 18 | 19 | if (error || !data) { 20 | throw new Error(error?.message || "Error fetching projects"); 21 | } 22 | 23 | projects.value = data.map((p) => { 24 | return { 25 | ...p, 26 | category: p.categories || undefined, 27 | }; 28 | }); 29 | 30 | if (!count) throw new Error("Count not fetched from Supabase"); 31 | pagination.value = getPaginationObject(count); 32 | }; 33 | 34 | const fetchOne = async ({ uuid }: { uuid: UuidT }) => { 35 | const { data, error } = await supabase 36 | .from("projects") 37 | .select("*") 38 | .eq("uuid", uuid) 39 | .single(); 40 | if (error || !data) { 41 | throw new Error(error?.message || "Error fetching project"); 42 | } 43 | project.value = data; 44 | }; 45 | 46 | const create = async (project: Partial): Promise => { 47 | const { data: newProject, error } = await supabase 48 | .from("projects") 49 | .insert(project as ProjectT) 50 | .select("*") 51 | .single(); 52 | 53 | if (error || !newProject) 54 | throw new Error(error?.message || "Error creating project"); 55 | 56 | return newProject; 57 | }; 58 | 59 | return { 60 | item: project, 61 | list: projects, 62 | pagination, 63 | 64 | create, 65 | fetchAll, 66 | fetchOne, 67 | }; 68 | }; 69 | -------------------------------------------------------------------------------- /nuxt/composables/useTypedSupabaseClient.ts: -------------------------------------------------------------------------------- 1 | import type { Database } from "@/supabase/schema"; 2 | 3 | export const useTypedSupabaseClient = () => { 4 | return useSupabaseClient(); 5 | }; 6 | -------------------------------------------------------------------------------- /nuxt/middleware/auth.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtRouteMiddleware((to, _from) => { 2 | const user = useSupabaseUser(); 3 | 4 | if (!user.value) { 5 | return navigateTo(`/login?redirect=${to.fullPath}`); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /nuxt/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | // https://nuxt.com/docs/api/configuration/nuxt-config 2 | export default defineNuxtConfig({ 3 | devtools: { enabled: true }, 4 | modules: [ 5 | "@nuxtjs/tailwindcss", 6 | "@nuxtjs/color-mode", 7 | "@nuxtjs/supabase", 8 | "@vueuse/nuxt", 9 | "@vee-validate/nuxt", 10 | "nuxt-proxy", 11 | ], 12 | colorMode: { 13 | preference: "cupcake", // default theme 14 | dataValue: "theme", // activate data-theme in tag 15 | }, 16 | proxy: { 17 | options: { 18 | target: "http://localhost:55321", 19 | changeOrigin: true, 20 | pathFilter: ["/rest/**", "/auth/**", "/storage/**"], 21 | }, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /nuxt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-app", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "build": "nuxt build", 7 | "supabase:start": "npx supabase start", 8 | "supabase:stop": "npx supabase stop", 9 | "dev": "nuxt dev", 10 | "generate": "nuxt generate", 11 | "preview": "nuxt preview", 12 | "postinstall": "nuxt prepare", 13 | "db:reset": "npx supabase db reset && yarn db:types", 14 | "db:types": "supabase gen types typescript --local > ./supabase/schema.ts", 15 | "wdio": "wdio run ./wdio.conf.ts" 16 | }, 17 | "devDependencies": { 18 | "@faker-js/faker": "^9.4.0", 19 | "@nuxt/devtools": "latest", 20 | "@nuxtjs/supabase": "^1.4.6", 21 | "@testing-library/jest-dom": "^6.6.3", 22 | "@testing-library/vue": "^8.1.0", 23 | "@types/mocha": "^10.0.10", 24 | "@types/node": "^22", 25 | "@vitejs/plugin-vue": "^5.2.1", 26 | "@wdio/browser-runner": "^9.7.3", 27 | "@wdio/cli": "^9.7.3", 28 | "@wdio/mocha-framework": "^9.7.3", 29 | "@wdio/spec-reporter": "^9.6.3", 30 | "nuxt": "^3.15.4", 31 | "supabase": "^2.9.6", 32 | "ts-node": "^10.9.2", 33 | "typescript": "^5.7.3" 34 | }, 35 | "dependencies": { 36 | "@nuxtjs/color-mode": "^3.5.2", 37 | "@nuxtjs/tailwindcss": "^6.13.1", 38 | "@vee-validate/nuxt": "^4.15.0", 39 | "@vee-validate/zod": "^4.15.0", 40 | "@vueuse/core": "^12.5.0", 41 | "@vueuse/nuxt": "^12.5.0", 42 | "autoprefixer": "^10.4.20", 43 | "daisyui": "^4.12.23", 44 | "dotenv": "^16.4.7", 45 | "nanoid": "^5.0.9", 46 | "nuxt-proxy": "^0.4.1", 47 | "postcss": "^8.5.1", 48 | "tailwindcss": "^4.0.4", 49 | "vee-validate": "^4.15.0", 50 | "zod": "^3.24.1" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /nuxt/pages/categories/[uuid].vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /nuxt/pages/connect-wallet.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /nuxt/pages/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /nuxt/pages/login.vue: -------------------------------------------------------------------------------- 1 | 28 | 74 | 75 | 80 | -------------------------------------------------------------------------------- /nuxt/pages/logout.vue: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /nuxt/pages/profile.vue: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /nuxt/pages/projects/[uuid].vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /nuxt/pages/projects/create.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /nuxt/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdriverio/component-testing-examples/9355c75f233954e50eaba77ba4a1c882c25c1c21/nuxt/public/favicon.ico -------------------------------------------------------------------------------- /nuxt/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /nuxt/supabase/.gitignore: -------------------------------------------------------------------------------- 1 | # Supabase 2 | .branches 3 | .temp 4 | -------------------------------------------------------------------------------- /nuxt/supabase/functions/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "editor.defaultFormatter": "denoland.vscode-deno" 5 | } 6 | -------------------------------------------------------------------------------- /nuxt/supabase/migrations/20230717154337_initial_schema.sql: -------------------------------------------------------------------------------- 1 | create table "public"."categories" ( 2 | "uuid" uuid not null default gen_random_uuid(), 3 | "name" character varying(255) not null, 4 | "slug" character varying(255) not null 5 | ); 6 | 7 | ALTER TABLE "public"."categories" ENABLE ROW LEVEL SECURITY; 8 | CREATE POLICY view_categories_policy ON "public"."categories" FOR SELECT 9 | USING (true); 10 | 11 | 12 | create table "public"."projects" ( 13 | "uuid" uuid not null default gen_random_uuid(), 14 | "title" character varying(255) not null, 15 | "excerpt" text not null, 16 | "description" text not null, 17 | "image" character varying(255) not null, 18 | "categoryUuid" uuid not null, 19 | "pledged" numeric not null default 0, 20 | "backers" integer not null default 0, 21 | "funded" character varying(255) not null default '0', 22 | "softCap" character varying(255) not null, 23 | "hardCap" character varying(255) not null, 24 | "startsAt" timestamp without time zone not null, 25 | "finishesAt" timestamp without time zone not null, 26 | "createdAt" timestamp without time zone not null default now(), 27 | "lastUpdatedAt" timestamp without time zone not null default now() 28 | ); 29 | 30 | ALTER TABLE "public"."projects" ENABLE ROW LEVEL SECURITY; 31 | CREATE POLICY view_projects_policy ON "public"."projects" FOR SELECT 32 | USING (true); 33 | 34 | CREATE POLICY "create_project_policy" 35 | ON "public"."projects" 36 | FOR INSERT 37 | TO authenticated 38 | WITH CHECK (true); 39 | 40 | CREATE UNIQUE INDEX categories_pkey ON public.categories USING btree (uuid); 41 | 42 | CREATE UNIQUE INDEX projects_pkey ON public.projects USING btree (uuid); 43 | 44 | alter table "public"."categories" add constraint "categories_pkey" PRIMARY KEY using index "categories_pkey"; 45 | 46 | alter table "public"."projects" add constraint "projects_pkey" PRIMARY KEY using index "projects_pkey"; 47 | 48 | alter table "public"."projects" add constraint "projects_categoryUuid_fkey" FOREIGN KEY ("categoryUuid") REFERENCES categories(uuid) not valid; 49 | 50 | alter table "public"."projects" validate constraint "projects_categoryUuid_fkey"; 51 | 52 | CREATE POLICY "logged in users can upload project images 1iiiika_0" ON storage.objects FOR INSERT TO authenticated WITH CHECK (bucket_id = 'projects'); -------------------------------------------------------------------------------- /nuxt/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./components/**/*.{js,vue,ts}", 5 | "./layouts/**/*.vue", 6 | "./pages/**/*.vue", 7 | "./plugins/**/*.{js,ts}", 8 | "./nuxt.config.{js,ts}", 9 | "./app.vue", 10 | ], 11 | theme: { 12 | extend: {}, 13 | }, 14 | plugins: [require("daisyui")], 15 | daisyui: { 16 | themes: ["cupcake", "dark", "cmyk"], 17 | }, 18 | safelist: [ 19 | "border-t-warning", 20 | "border-t-error", 21 | "border-t-success", 22 | "border-t-info", 23 | ], 24 | }; 25 | -------------------------------------------------------------------------------- /nuxt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json", 4 | "compilerOptions": { 5 | "types": ["node", "mocha"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /nuxt/types/index.ts: -------------------------------------------------------------------------------- 1 | import type { Database } from "@/supabase/schema"; 2 | export type UuidT = string; 3 | 4 | export type CategoryT = Database["public"]["Tables"]["categories"]["Row"]; 5 | 6 | export type ProjectT = Database["public"]["Tables"]["projects"]["Row"] & { 7 | category?: CategoryT; 8 | }; 9 | 10 | export type PaginationT = { 11 | total: number; 12 | page: number; 13 | limit: number; 14 | pages: number; 15 | isNextAvailable: boolean; 16 | isPrevAvailable: boolean; 17 | next: number | null; 18 | prev: number | null; 19 | }; 20 | 21 | export type ApiResponseT = { 22 | data: any; 23 | pagination: PaginationT; 24 | }; 25 | -------------------------------------------------------------------------------- /nuxt/utils/index.ts: -------------------------------------------------------------------------------- 1 | export function getDateXMonthsFromNow(months: number) { 2 | return new Date(new Date().setMonth(new Date().getMonth() + months)); 3 | } 4 | -------------------------------------------------------------------------------- /preact-typescript-vite/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /preact-typescript-vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + Preact + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /preact-typescript-vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "preact-typescript-vite", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview", 10 | "wdio": "wdio run ./wdio.conf.ts" 11 | }, 12 | "dependencies": { 13 | "preact": "^10.25.4" 14 | }, 15 | "devDependencies": { 16 | "@preact/preset-vite": "^2.10.1", 17 | "@testing-library/preact": "^3.2.4", 18 | "@types/mocha": "^10.0.10", 19 | "@wdio/browser-runner": "^9.7.3", 20 | "@wdio/cli": "^9.7.3", 21 | "@wdio/globals": "^9.7.3", 22 | "@wdio/mocha-framework": "^9.7.3", 23 | "@wdio/spec-reporter": "^9.6.3", 24 | "ts-node": "^10.9.2", 25 | "typescript": "^5.7.3", 26 | "vite": "^6.1.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /preact-typescript-vite/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /preact-typescript-vite/src/app.css: -------------------------------------------------------------------------------- 1 | #app { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | } 12 | .logo:hover { 13 | filter: drop-shadow(0 0 2em #646cffaa); 14 | } 15 | .logo.preact:hover { 16 | filter: drop-shadow(0 0 2em #673ab8aa); 17 | } 18 | 19 | .card { 20 | padding: 2em; 21 | } 22 | 23 | .read-the-docs { 24 | color: #888; 25 | } 26 | -------------------------------------------------------------------------------- /preact-typescript-vite/src/app.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'preact/hooks' 2 | import preactLogo from './assets/preact.svg' 3 | import './app.css' 4 | 5 | export function App() { 6 | const [count, setCount] = useState(0) 7 | 8 | return ( 9 | <> 10 | 18 |

Vite + Preact

19 |
20 | 23 |

24 | Edit src/app.tsx and save to test HMR 25 |

26 |
27 |

28 | Click on the Vite and Preact logos to learn more 29 |

30 | 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /preact-typescript-vite/src/assets/preact.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /preact-typescript-vite/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color-scheme: light dark; 8 | color: rgba(255, 255, 255, 0.87); 9 | background-color: #242424; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | a { 19 | font-weight: 500; 20 | color: #646cff; 21 | text-decoration: inherit; 22 | } 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | body { 28 | margin: 0; 29 | display: flex; 30 | place-items: center; 31 | min-width: 320px; 32 | min-height: 100vh; 33 | } 34 | 35 | h1 { 36 | font-size: 3.2em; 37 | line-height: 1.1; 38 | } 39 | 40 | button { 41 | border-radius: 8px; 42 | border: 1px solid transparent; 43 | padding: 0.6em 1.2em; 44 | font-size: 1em; 45 | font-weight: 500; 46 | font-family: inherit; 47 | background-color: #1a1a1a; 48 | cursor: pointer; 49 | transition: border-color 0.25s; 50 | } 51 | button:hover { 52 | border-color: #646cff; 53 | } 54 | button:focus, 55 | button:focus-visible { 56 | outline: 4px auto -webkit-focus-ring-color; 57 | } 58 | 59 | @media (prefers-color-scheme: light) { 60 | :root { 61 | color: #213547; 62 | background-color: #ffffff; 63 | } 64 | a:hover { 65 | color: #747bff; 66 | } 67 | button { 68 | background-color: #f9f9f9; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /preact-typescript-vite/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { render } from 'preact' 2 | import { App } from './app' 3 | import './index.css' 4 | 5 | render(, document.getElementById('app') as HTMLElement) 6 | -------------------------------------------------------------------------------- /preact-typescript-vite/src/tests/HelloWorld.test.tsx: -------------------------------------------------------------------------------- 1 | import { h } from 'preact' 2 | import { $, expect } from '@wdio/globals' 3 | import { render } from '@testing-library/preact' 4 | import { App } from '../app' 5 | 6 | describe('Vue Component Testing', () => { 7 | it('increments value on click', async () => { 8 | // The render method returns a collection of utilities to query your component. 9 | const { getByText } = render() 10 | 11 | // getByText returns the first matching node for the provided text, and 12 | // throws an error if no elements match or if more than one match is found. 13 | const btn = getByText('count is 0') 14 | const button = await $(btn) 15 | 16 | // Dispatch a native click event to our button element. 17 | await button.click() 18 | await button.click() 19 | 20 | await expect($('button*=count is')).toHaveText('count is 2') 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /preact-typescript-vite/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /preact-typescript-vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "jsxImportSource": "preact", 19 | "ignoreDeprecations": "5.0" 20 | }, 21 | "include": ["src"], 22 | "references": [{ "path": "./tsconfig.node.json" }] 23 | } 24 | -------------------------------------------------------------------------------- /preact-typescript-vite/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /preact-typescript-vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import preact from '@preact/preset-vite' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [preact()] 7 | }) 8 | -------------------------------------------------------------------------------- /react-typescript-vite/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /react-typescript-vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /react-typescript-vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-typescript-vite", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview", 10 | "wdio": "wdio run ./wdio.conf.ts" 11 | }, 12 | "dependencies": { 13 | "react": "^19.0.0", 14 | "react-dom": "^19.0.0" 15 | }, 16 | "devDependencies": { 17 | "@testing-library/react": "^16.2.0", 18 | "@types/mocha": "^10.0.10", 19 | "@types/react": "^19.0.8", 20 | "@types/react-dom": "^19.0.3", 21 | "@vitejs/plugin-react": "^4.3.4", 22 | "@wdio/browser-runner": "^9.7.3", 23 | "@wdio/cli": "^9.7.3", 24 | "@wdio/globals": "^9.7.3", 25 | "@wdio/mocha-framework": "^9.7.3", 26 | "@wdio/spec-reporter": "^9.6.3", 27 | "ts-node": "^10.9.2", 28 | "typescript": "^5.7.3", 29 | "vite": "^6.1.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /react-typescript-vite/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-typescript-vite/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | } 13 | .logo:hover { 14 | filter: drop-shadow(0 0 2em #646cffaa); 15 | } 16 | .logo.react:hover { 17 | filter: drop-shadow(0 0 2em #61dafbaa); 18 | } 19 | 20 | @keyframes logo-spin { 21 | from { 22 | transform: rotate(0deg); 23 | } 24 | to { 25 | transform: rotate(360deg); 26 | } 27 | } 28 | 29 | @media (prefers-reduced-motion: no-preference) { 30 | a:nth-of-type(2) .logo { 31 | animation: logo-spin infinite 20s linear; 32 | } 33 | } 34 | 35 | .card { 36 | padding: 2em; 37 | } 38 | 39 | .read-the-docs { 40 | color: #888; 41 | } 42 | -------------------------------------------------------------------------------- /react-typescript-vite/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import reactLogo from './assets/react.svg' 3 | import './App.css' 4 | 5 | function App() { 6 | const [count, setCount] = useState(0) 7 | 8 | return ( 9 |
10 | 18 |

Vite + React

19 |
20 | 23 |

24 | Edit src/App.tsx and save to test HMR 25 |

26 |
27 |

28 | Click on the Vite and React logos to learn more 29 |

30 |
31 | ) 32 | } 33 | 34 | export default App 35 | -------------------------------------------------------------------------------- /react-typescript-vite/src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default (props: React.DetailedHTMLProps, HTMLButtonElement>) => { 4 | const { className, children, ...rest } = props; 5 | return ( 6 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /react-typescript-vite/src/components/InputField.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface Props extends React.DetailedHTMLProps, HTMLInputElement> { 4 | className?: string 5 | label?: string 6 | submitted?: boolean 7 | requiredMessage: string 8 | value: any 9 | name: string 10 | } 11 | 12 | const InputField = (props: Props) => { 13 | const { className, label, submitted, requiredMessage, ...inputProps } = props; 14 | return ( 15 | 25 | ); 26 | }; 27 | 28 | export default InputField; 29 | -------------------------------------------------------------------------------- /react-typescript-vite/src/components/LoginForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import Button from './Button'; 3 | import InputField from './InputField'; 4 | 5 | interface Props { 6 | onLogin: (credentials: { username: string, password: string }) => void 7 | title?: string 8 | errorMessage?: string 9 | } 10 | 11 | const LoginForm = ({ onLogin, title = 'Log In', errorMessage }: Props) => { 12 | const [submitted, setSubmitted] = useState(false); 13 | const [username, setUsername] = useState(''); 14 | const [password, setPassword] = useState(''); 15 | 16 | const handleFormSubmit = (event: React.FormEvent) => { 17 | event.preventDefault(); 18 | if (username && password) { 19 | onLogin({ username, password }); 20 | } 21 | setSubmitted(true); 22 | }; 23 | 24 | return ( 25 |
26 |
27 |
28 | {title} 29 | setUsername(e.target.value)} 36 | value={username} 37 | autoComplete="username" 38 | /> 39 | setPassword(e.target.value)} 46 | value={password} 47 | autoComplete="current-password" 48 | /> 49 | 50 | {errorMessage && ( 51 |
{errorMessage}
52 | )} 53 |
54 |
55 |
56 | ); 57 | }; 58 | 59 | export default LoginForm; 60 | -------------------------------------------------------------------------------- /react-typescript-vite/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color-scheme: light dark; 8 | color: rgba(255, 255, 255, 0.87); 9 | background-color: #242424; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | a { 19 | font-weight: 500; 20 | color: #646cff; 21 | text-decoration: inherit; 22 | } 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | body { 28 | margin: 0; 29 | display: flex; 30 | place-items: center; 31 | min-width: 320px; 32 | min-height: 100vh; 33 | } 34 | 35 | h1 { 36 | font-size: 3.2em; 37 | line-height: 1.1; 38 | } 39 | 40 | button { 41 | border-radius: 8px; 42 | border: 1px solid transparent; 43 | padding: 0.6em 1.2em; 44 | font-size: 1em; 45 | font-weight: 500; 46 | font-family: inherit; 47 | background-color: #1a1a1a; 48 | cursor: pointer; 49 | transition: border-color 0.25s; 50 | } 51 | button:hover { 52 | border-color: #646cff; 53 | } 54 | button:focus, 55 | button:focus-visible { 56 | outline: 4px auto -webkit-focus-ring-color; 57 | } 58 | 59 | @media (prefers-color-scheme: light) { 60 | :root { 61 | color: #213547; 62 | background-color: #ffffff; 63 | } 64 | a:hover { 65 | color: #747bff; 66 | } 67 | button { 68 | background-color: #f9f9f9; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /react-typescript-vite/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 7 | 8 | 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /react-typescript-vite/src/tests/HelloWorld.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { $, expect } from '@wdio/globals' 3 | import { render } from '@testing-library/react' 4 | import App from '../App' 5 | 6 | describe('React Component Testing', () => { 7 | it('increments value on click', async () => { 8 | // The render method returns a collection of utilities to query your component. 9 | const { getByText } = render() 10 | 11 | // getByText returns the first matching node for the provided text, and 12 | // throws an error if no elements match or if more than one match is found. 13 | const btn = getByText('count is 0') 14 | const button = await $(btn) 15 | 16 | // Dispatch a native click event to our button element. 17 | await button.click() 18 | await button.click() 19 | 20 | await expect($('button*=count is')).toHaveText('count is 2') 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /react-typescript-vite/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /react-typescript-vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "ignoreDeprecations": "5.0" 19 | }, 20 | "include": ["src"], 21 | "references": [{ "path": "./tsconfig.node.json" }] 22 | } 23 | -------------------------------------------------------------------------------- /react-typescript-vite/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /react-typescript-vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()] 7 | }) 8 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /solidjs-typescript-vite/README.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | 3 | Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`. 4 | 5 | This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template. 6 | 7 | ```bash 8 | $ npm install # or pnpm install or yarn install 9 | ``` 10 | 11 | ### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs) 12 | 13 | ## Available Scripts 14 | 15 | In the project directory, you can run: 16 | 17 | ### `npm dev` or `npm start` 18 | 19 | Runs the app in the development mode.
20 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 21 | 22 | The page will reload if you make edits.
23 | 24 | ### `npm run build` 25 | 26 | Builds the app for production to the `dist` folder.
27 | It correctly bundles Solid in production mode and optimizes the build for the best performance. 28 | 29 | The build is minified and the filenames include the hashes.
30 | Your app is ready to be deployed! 31 | 32 | ## Deployment 33 | 34 | You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.) 35 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Solid App 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-template-solid", 3 | "version": "0.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "vite", 7 | "dev": "vite", 8 | "build": "vite build", 9 | "serve": "vite preview", 10 | "wdio": "wdio run ./wdio.conf.ts", 11 | "wdio:sauce": "wdio run ./wdio.sauce.conf.ts" 12 | }, 13 | "type": "module", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "@wdio/browser-runner": "^9.7.3", 17 | "@wdio/cli": "^9.7.3", 18 | "@wdio/mocha-framework": "^9.7.3", 19 | "@wdio/sauce-service": "^9.7.3", 20 | "@wdio/spec-reporter": "^9.6.3", 21 | "solid-testing-library": "^0.5.1", 22 | "ts-node": "^10.9.2", 23 | "typescript": "^5.7.3", 24 | "vite": "^6.1.0", 25 | "vite-plugin-solid": "^2.11.1" 26 | }, 27 | "dependencies": { 28 | "solid-js": "^1.9.4" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/src/App.module.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .logo { 6 | animation: logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .header { 12 | background-color: #282c34; 13 | min-height: 100vh; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | font-size: calc(10px + 2vmin); 19 | color: white; 20 | } 21 | 22 | .link { 23 | color: #b318f0; 24 | } 25 | 26 | @keyframes logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | to { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/src/App.tsx: -------------------------------------------------------------------------------- 1 | import type { Component } from 'solid-js'; 2 | 3 | import logo from './logo.svg'; 4 | import styles from './App.module.css'; 5 | 6 | const App: Component = () => { 7 | return ( 8 |
9 |
10 | logo 11 |

12 | Edit src/App.tsx and save to reload. 13 |

14 | 20 | Learn Solid 21 | 22 |
23 |
24 | ); 25 | }; 26 | 27 | export default App; 28 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdriverio/component-testing-examples/9355c75f233954e50eaba77ba4a1c882c25c1c21/solidjs-typescript-vite/src/assets/favicon.ico -------------------------------------------------------------------------------- /solidjs-typescript-vite/src/components/counter.tsx: -------------------------------------------------------------------------------- 1 | import { createSignal, Component } from "solid-js"; 2 | 3 | export const Counter: Component = () => { 4 | const [count, setCount] = createSignal(0); 5 | 6 | return ( 7 |
setCount((c) => c + 1)}> 8 | Count: {count()} 9 |
10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/src/components/modal.css: -------------------------------------------------------------------------------- 1 | .modal { 2 | padding: 16px; 3 | border: 1px solid #444; 4 | box-shadow: 4px 4px #88888866; 5 | } 6 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/src/components/modal.tsx: -------------------------------------------------------------------------------- 1 | import { createSignal, Show } from "solid-js"; 2 | import clickOutside from '../directives/click-outside.js'; 3 | import './modal.css'; 4 | 5 | export default function Modal () { 6 | const [show, setShow] = createSignal(false); 7 | console.log(clickOutside); // needed otherwise not imported (ReferenceError: clickOutside is not defined) 8 | 9 | return ( 10 | setShow(true)}>Open Modal} 13 | > 14 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/src/directives/click-outside.tsx: -------------------------------------------------------------------------------- 1 | import { onCleanup } from "solid-js"; 2 | 3 | export default function clickOutside(el: any, accessor: Function) { 4 | const onClick = (e: Event) => !el.contains(e.target) && accessor()?.(); 5 | document.body.addEventListener("click", onClick); 6 | 7 | onCleanup(() => document.body.removeEventListener("click", onClick)); 8 | } 9 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/src/index.tsx: -------------------------------------------------------------------------------- 1 | /* @refresh reload */ 2 | import { render } from 'solid-js/web'; 3 | 4 | import './index.css'; 5 | import App from './App'; 6 | 7 | render(() => , document.getElementById('root') as HTMLElement); 8 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/src/tests/counter.test.tsx: -------------------------------------------------------------------------------- 1 | import { browser, $, expect } from '@wdio/globals' 2 | import { cleanup, render, screen } from 'solid-testing-library' 3 | 4 | import { Counter } from '../components/counter' 5 | 6 | describe('my component tests', () => { 7 | afterEach(cleanup) 8 | 9 | it('it starts with zero', async () => { 10 | render(() => ) 11 | const button = screen.getByRole('button'); 12 | expect($(button)).toBePresent(); 13 | expect($(button)).toHaveText( 14 | expect.stringContaining('Count: 0')); 15 | }); 16 | 17 | it('it increases its value on click', async () => { 18 | render(() => ); 19 | const button = screen.getByRole('button'); 20 | await $(button).click() 21 | expect($(button)).toHaveText( 22 | expect.stringContaining('Count: 1')); 23 | await $(button).click() 24 | expect(button).toHaveText( 25 | expect.stringContaining('Count: 2')); 26 | }); 27 | }) 28 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/src/tests/example.test.tsx: -------------------------------------------------------------------------------- 1 | import { $, expect } from '@wdio/globals' 2 | import { render } from 'solid-js/web' 3 | 4 | import App from '../App' 5 | 6 | describe('my component tests', () => { 7 | it('should do something cool', async () => { 8 | render(() => , document.body as HTMLElement) 9 | await expect($('p')).toHaveText('Edit src/App.tsx and save to reload.') 10 | }) 11 | }) 12 | 13 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/src/tests/modal.test.tsx: -------------------------------------------------------------------------------- 1 | import { browser, $, expect } from '@wdio/globals' 2 | import { render } from 'solid-js/web' 3 | 4 | import Modal from '../components/modal' 5 | 6 | describe('my component tests', () => { 7 | const ref = document.createElement('div') 8 | 9 | before(() => { 10 | document.body.parentElement!.style.height = '100%' 11 | document.body.style.height = '100%'; 12 | document.body.appendChild(ref); 13 | }) 14 | 15 | after(() => { 16 | document.body.removeChild(ref); 17 | }) 18 | 19 | it('will trigger on click outside', async () => { 20 | render(() => , ref) 21 | await expect($('.modal')).not.toBePresent() 22 | await $('button').click() 23 | await expect($('.modal')).toBePresent() 24 | await $('body').click() 25 | await expect($('.modal')).not.toBePresent() 26 | }) 27 | 28 | it('will not trigger on click inside', async () => { 29 | render(() => , ref) 30 | await expect($('.modal')).not.toBePresent() 31 | await $('button').click() 32 | await expect($('.modal')).toBePresent() 33 | await $('.modal').click() 34 | await expect($('.modal')).toBePresent() 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "node", 7 | "allowSyntheticDefaultImports": true, 8 | "esModuleInterop": true, 9 | "jsx": "preserve", 10 | "jsxImportSource": "solid-js", 11 | "types": ["vite/client", "@wdio/mocha-framework"], 12 | "noEmit": true, 13 | "isolatedModules": true, 14 | "ignoreDeprecations": "5.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import solidPlugin from 'vite-plugin-solid'; 3 | 4 | export default defineConfig({ 5 | plugins: [solidPlugin()], 6 | server: { 7 | port: 3000, 8 | }, 9 | build: { 10 | target: 'esnext', 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /solidjs-typescript-vite/wdio.sauce.conf.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from '@wdio/types' 2 | import { config as baseConfig } from './wdio.conf.js' 3 | 4 | /** 5 | * With this config you can run all your SolidJS component tests in a cloud 6 | * using an environment that you don't have available locally or in CI. 7 | * To run the example, get a free-trial Sauce Labs account and export your 8 | * credentials into your environment, then run: 9 | * 10 | * $ npm run wdio:sauce 11 | */ 12 | export const config: Options.Testrunner = { 13 | ...baseConfig, 14 | user: process.env.SAUCE_USERNAME, 15 | key: process.env.SAUCE_ACCESS_KEY, 16 | /** 17 | * for better reporting and integration into Sauce Labs 18 | */ 19 | services: [['sauce', { 20 | /** 21 | * required for making Vite server accessible to cloud browser 22 | */ 23 | sauceConnect: true 24 | }]], 25 | capabilities: [{ 26 | /** 27 | * this just tests for one (Android) environment but you 28 | * can add arbitrarily more capabilities to this 29 | */ 30 | platformName: 'Android', 31 | browserName: 'Chrome', 32 | 'appium:deviceName': 'Android GoogleAPI Emulator', 33 | 'appium:platformVersion': '12.0', 34 | 'appium:automationName': 'UiAutomator2', 35 | 'sauce:options': { 36 | // @ts-expect-error capability not defined within WebdriverIO (yet) 37 | appiumVersion: '1.22.1' 38 | } 39 | }] 40 | } 41 | -------------------------------------------------------------------------------- /stencil-component-starter/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /stencil-component-starter/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | www/ 3 | loader/ 4 | 5 | *~ 6 | *.sw[mnpcod] 7 | *.log 8 | *.lock 9 | *.tmp 10 | *.tmp.* 11 | log.txt 12 | *.sublime-project 13 | *.sublime-workspace 14 | 15 | .stencil/ 16 | .idea/ 17 | .vscode/ 18 | .sass-cache/ 19 | .versions/ 20 | node_modules/ 21 | $RECYCLE.BIN/ 22 | 23 | .DS_Store 24 | Thumbs.db 25 | UserInterfaceState.xcuserstate 26 | .env 27 | -------------------------------------------------------------------------------- /stencil-component-starter/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": true, 4 | "jsxBracketSameLine": false, 5 | "jsxSingleQuote": false, 6 | "quoteProps": "consistent", 7 | "printWidth": 180, 8 | "semi": true, 9 | "singleQuote": true, 10 | "tabWidth": 2, 11 | "trailingComma": "all", 12 | "useTabs": false 13 | } 14 | -------------------------------------------------------------------------------- /stencil-component-starter/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /stencil-component-starter/__snapshots__/MyComponent-chrome-1200x1551-dpr-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webdriverio/component-testing-examples/9355c75f233954e50eaba77ba4a1c882c25c1c21/stencil-component-starter/__snapshots__/MyComponent-chrome-1200x1551-dpr-2.png -------------------------------------------------------------------------------- /stencil-component-starter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stencil-starter-project-name", 3 | "version": "0.0.1", 4 | "description": "Stencil Component Starter", 5 | "module": "dist/index.js", 6 | "types": "dist/types/index.d.ts", 7 | "type": "module", 8 | "collection": "dist/collection/collection-manifest.json", 9 | "collection:main": "dist/collection/index.js", 10 | "unpkg": "dist/stencil-starter-project-name/stencil-starter-project-name.esm.js", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/ionic-team/stencil-component-starter.git" 14 | }, 15 | "files": [ 16 | "dist/", 17 | "loader/" 18 | ], 19 | "scripts": { 20 | "build": "stencil build --docs", 21 | "start": "stencil build --dev --watch --serve", 22 | "test": "stencil test --spec --e2e", 23 | "test.watch": "stencil test --spec --e2e --watchAll", 24 | "generate": "stencil generate", 25 | "wdio": "wdio run ./wdio.conf.ts" 26 | }, 27 | "devDependencies": { 28 | "@stencil/core": "^4.25.3", 29 | "@types/jest": "^29.5.14", 30 | "@types/node": "^22.13.1", 31 | "@wdio/browser-runner": "^9.7.3", 32 | "@wdio/cli": "^9.7.3", 33 | "@wdio/mocha-framework": "^9.7.3", 34 | "@wdio/spec-reporter": "^9.6.3", 35 | "@wdio/visual-service": "^6.3.1", 36 | "ts-node": "^10.9.2", 37 | "typescript": "^5.7.3" 38 | }, 39 | "license": "MIT", 40 | "dependencies": { 41 | "mlly": "^1.7.4" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /stencil-component-starter/src/components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* tslint:disable */ 3 | /** 4 | * This is an autogenerated file created by the Stencil compiler. 5 | * It contains typing information for all components that exist in this project. 6 | */ 7 | import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; 8 | export namespace Components { 9 | interface MyComponent { 10 | /** 11 | * The first name 12 | */ 13 | "first": string; 14 | /** 15 | * The last name 16 | */ 17 | "last": string; 18 | /** 19 | * The middle name 20 | */ 21 | "middle": string; 22 | } 23 | } 24 | declare global { 25 | interface HTMLMyComponentElement extends Components.MyComponent, HTMLStencilElement { 26 | } 27 | var HTMLMyComponentElement: { 28 | prototype: HTMLMyComponentElement; 29 | new (): HTMLMyComponentElement; 30 | }; 31 | interface HTMLElementTagNameMap { 32 | "my-component": HTMLMyComponentElement; 33 | } 34 | } 35 | declare namespace LocalJSX { 36 | interface MyComponent { 37 | /** 38 | * The first name 39 | */ 40 | "first"?: string; 41 | /** 42 | * The last name 43 | */ 44 | "last"?: string; 45 | /** 46 | * The middle name 47 | */ 48 | "middle"?: string; 49 | } 50 | interface IntrinsicElements { 51 | "my-component": MyComponent; 52 | } 53 | } 54 | export { LocalJSX as JSX }; 55 | declare module "@stencil/core" { 56 | export namespace JSX { 57 | interface IntrinsicElements { 58 | "my-component": LocalJSX.MyComponent & JSXBase.HTMLAttributes; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /stencil-component-starter/src/components/my-component/my-component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | } 4 | 5 | span { 6 | font-weight: bold; 7 | } 8 | -------------------------------------------------------------------------------- /stencil-component-starter/src/components/my-component/my-component.test.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | import { h } from '@stencil/core' 3 | import { $, expect } from '@wdio/globals'; 4 | import { render } from '@wdio/browser-runner/stencil'; 5 | 6 | import { MyComponent } from './my-component.js'; 7 | 8 | describe('my-component', () => { 9 | it('renders the component correctly', async () => { 10 | render({ 11 | components: [MyComponent], 12 | template: () => 13 | }); 14 | 15 | const component = $('my-component') 16 | await expect(component).toHaveText(`Hello, World! I'm Stencil 'Don't call me a framework' JS`) 17 | await expect((await $('>>>span').getCSSProperty('font-weight')).value).toBe(700) 18 | }); 19 | 20 | it('looks visually perfect', async () => { 21 | render({ 22 | components: [MyComponent], 23 | template: () => 24 | }); 25 | 26 | const component = $('my-component') 27 | await expect(component).toMatchElementSnapshot('MyComponent') 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /stencil-component-starter/src/components/my-component/my-component.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Prop, h } from '@stencil/core'; 2 | import { format } from '../../utils/utils.js'; 3 | 4 | @Component({ 5 | tag: 'my-component', 6 | styleUrl: 'my-component.css', 7 | shadow: true, 8 | }) 9 | export class MyComponent { 10 | /** 11 | * The first name 12 | */ 13 | @Prop() first: string; 14 | 15 | /** 16 | * The middle name 17 | */ 18 | @Prop() middle: string; 19 | 20 | /** 21 | * The last name 22 | */ 23 | @Prop() last: string; 24 | 25 | private getText(): string { 26 | return ( 27 | {format(this.first, this.middle, this.last)} 28 | ); 29 | } 30 | 31 | render() { 32 | return
Hello, World! I'm {this.getText()}
; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /stencil-component-starter/src/components/my-component/readme.md: -------------------------------------------------------------------------------- 1 | # my-component 2 | 3 | 4 | 5 | 6 | 7 | 8 | ## Properties 9 | 10 | | Property | Attribute | Description | Type | Default | 11 | | -------- | --------- | --------------- | -------- | ----------- | 12 | | `first` | `first` | The first name | `string` | `undefined` | 13 | | `last` | `last` | The last name | `string` | `undefined` | 14 | | `middle` | `middle` | The middle name | `string` | `undefined` | 15 | 16 | 17 | ---------------------------------------------- 18 | 19 | *Built with [StencilJS](https://stenciljs.com/)* 20 | -------------------------------------------------------------------------------- /stencil-component-starter/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stencil Component Starter 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /stencil-component-starter/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; 2 | -------------------------------------------------------------------------------- /stencil-component-starter/src/utils/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from '@wdio/globals'; 2 | import { format } from './utils.js'; 3 | 4 | describe('format', () => { 5 | it('returns empty string for no names defined', () => { 6 | expect(format(undefined, undefined, undefined)).toEqual(''); 7 | }); 8 | 9 | it('formats just first names', () => { 10 | expect(format('Joseph', undefined, undefined)).toEqual('Joseph'); 11 | }); 12 | 13 | it('formats first and last names', () => { 14 | expect(format('Joseph', undefined, 'Publique')).toEqual('Joseph Publique'); 15 | }); 16 | 17 | it('formats first, middle and last names', () => { 18 | expect(format('Joseph', 'Quincy', 'Publique')).toEqual('Joseph Quincy Publique'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /stencil-component-starter/src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | export function format(first: string, middle: string, last: string): string { 2 | return (first || '') + (middle ? ` ${middle}` : '') + (last ? ` ${last}` : ''); 3 | } 4 | -------------------------------------------------------------------------------- /stencil-component-starter/stencil.config.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '@stencil/core'; 2 | 3 | export const config: Config = { 4 | namespace: 'stencil-starter-project-name', 5 | outputTargets: [ 6 | { 7 | type: 'dist', 8 | esmLoaderPath: '../loader', 9 | }, 10 | { 11 | type: 'dist-custom-elements', 12 | }, 13 | { 14 | type: 'docs-readme', 15 | }, 16 | { 17 | type: 'www', 18 | serviceWorker: null, // disable service workers 19 | }, 20 | ], 21 | testing: { 22 | browserHeadless: "new", 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /stencil-component-starter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "allowUnreachableCode": false, 5 | "declaration": false, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "dom", 9 | "es2017" 10 | ], 11 | "moduleResolution": "node", 12 | "module": "esnext", 13 | "target": "es2017", 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "jsx": "react", 17 | "jsxFactory": "h", 18 | "jsxFragmentFactory": "Fragment", 19 | "skipLibCheck": true 20 | }, 21 | "include": [ 22 | "src" 23 | ], 24 | "exclude": [ 25 | "node_modules" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /svelte-typescript-vite/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /svelte-typescript-vite/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["svelte.svelte-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /svelte-typescript-vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + Svelte + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /svelte-typescript-vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-typescript-vite", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "check": "svelte-check --tsconfig ./tsconfig.json", 11 | "wdio": "wdio run ./wdio.conf.ts" 12 | }, 13 | "devDependencies": { 14 | "@sveltejs/vite-plugin-svelte": "^5.0.3", 15 | "@testing-library/svelte": "^5.2.6", 16 | "@tsconfig/svelte": "^5.0.4", 17 | "@types/mocha": "^10.0.10", 18 | "@wdio/browser-runner": "^9.7.3", 19 | "@wdio/cli": "^9.7.3", 20 | "@wdio/globals": "^9.7.3", 21 | "@wdio/mocha-framework": "^9.7.3", 22 | "@wdio/spec-reporter": "^9.6.3", 23 | "svelte": "^5.19.8", 24 | "svelte-check": "^4.1.4", 25 | "svelte-preprocess": "^6.0.3", 26 | "ts-node": "^10.9.2", 27 | "tslib": "^2.8.1", 28 | "typescript": "^5.7.3", 29 | "vite": "^6.1.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /svelte-typescript-vite/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svelte-typescript-vite/src/App.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | 15 |

Vite + Svelte

16 | 17 |
18 | 19 |
20 | 21 |

22 | Check out SvelteKit, the official Svelte app framework powered by Vite! 23 |

24 | 25 |

26 | Click on the Vite and Svelte logos to learn more 27 |

28 |
29 | 30 | -------------------------------------------------------------------------------- /svelte-typescript-vite/src/app.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color-scheme: light dark; 8 | color: rgba(255, 255, 255, 0.87); 9 | background-color: #242424; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | a { 19 | font-weight: 500; 20 | color: #646cff; 21 | text-decoration: inherit; 22 | } 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | body { 28 | margin: 0; 29 | display: flex; 30 | place-items: center; 31 | min-width: 320px; 32 | min-height: 100vh; 33 | } 34 | 35 | h1 { 36 | font-size: 3.2em; 37 | line-height: 1.1; 38 | } 39 | 40 | .card { 41 | padding: 2em; 42 | } 43 | 44 | #app { 45 | max-width: 1280px; 46 | margin: 0 auto; 47 | padding: 2rem; 48 | text-align: center; 49 | } 50 | 51 | button { 52 | border-radius: 8px; 53 | border: 1px solid transparent; 54 | padding: 0.6em 1.2em; 55 | font-size: 1em; 56 | font-weight: 500; 57 | font-family: inherit; 58 | background-color: #1a1a1a; 59 | cursor: pointer; 60 | transition: border-color 0.25s; 61 | } 62 | button:hover { 63 | border-color: #646cff; 64 | } 65 | button:focus, 66 | button:focus-visible { 67 | outline: 4px auto -webkit-focus-ring-color; 68 | } 69 | 70 | @media (prefers-color-scheme: light) { 71 | :root { 72 | color: #213547; 73 | background-color: #ffffff; 74 | } 75 | a:hover { 76 | color: #747bff; 77 | } 78 | button { 79 | background-color: #f9f9f9; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /svelte-typescript-vite/src/assets/svelte.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svelte-typescript-vite/src/lib/Counter.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /svelte-typescript-vite/src/lib/Hoverable.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /svelte-typescript-vite/src/lib/Longpress.svelte: -------------------------------------------------------------------------------- 1 | 32 | 33 | 37 | 38 | 43 | 44 | {#if pressed} 45 |

congratulations, you pressed and held for {duration}ms

46 | {/if} 47 | -------------------------------------------------------------------------------- /svelte-typescript-vite/src/main.ts: -------------------------------------------------------------------------------- 1 | import './app.css' 2 | import App from './App.svelte' 3 | 4 | const app = new App({ 5 | target: document.getElementById('app') 6 | }) 7 | 8 | export default app 9 | -------------------------------------------------------------------------------- /svelte-typescript-vite/src/tests/Counter.test.ts: -------------------------------------------------------------------------------- 1 | import { $, expect } from '@wdio/globals' 2 | import { render } from '@testing-library/svelte' 3 | import Counter from '../lib/Counter.svelte' 4 | 5 | describe('Svelte Component Testing', () => { 6 | it('increments value on click', async () => { 7 | // The render method returns a collection of utilities to query your component. 8 | const { getByText } = render(Counter) 9 | 10 | // getByText returns the first matching node for the provided text, and 11 | // throws an error if no elements match or if more than one match is found. 12 | const btn = getByText('count is 0') 13 | const button = await $(btn) 14 | 15 | // Dispatch a native click event to our button element. 16 | await button.click() 17 | await button.click() 18 | 19 | await expect($('button*=count is')).toHaveText('count is 2') 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /svelte-typescript-vite/src/tests/Hoverable.test.ts: -------------------------------------------------------------------------------- 1 | import { $$, expect } from '@wdio/globals' 2 | import { render } from '@testing-library/svelte' 3 | import Hoverable from './__fixtures__/Hoverable.svelte' 4 | 5 | const DEFAULT_TEXT = 'Hover over me!' 6 | const FIRST_ELEM_HOVERED_TEXT = '1st element is being hovered upon.' 7 | const SECOND_ELEM_HOVERED_TEXT = '2nd element is being hovered upon.' 8 | const THIRD_ELEM_HOVERED_TEXT = '3rd element is being hovered upon.' 9 | 10 | describe('Svelte Component Testing', () => { 11 | it('increments value on click', async () => { 12 | const { getAllByText, getByText, queryByText } = render(Hoverable) 13 | 14 | const elems = getAllByText(DEFAULT_TEXT) 15 | expect(elems).toHaveLength(3) 16 | 17 | const $elems = await $$(elems) 18 | 19 | await $elems[1].moveTo() 20 | expect(queryByText(FIRST_ELEM_HOVERED_TEXT)).not.toBeTruthy() 21 | expect(getByText(SECOND_ELEM_HOVERED_TEXT)).toBeTruthy() 22 | expect(queryByText(THIRD_ELEM_HOVERED_TEXT)).not.toBeTruthy() 23 | 24 | await $elems[2].moveTo() 25 | expect(queryByText(FIRST_ELEM_HOVERED_TEXT)).not.toBeTruthy() 26 | expect(queryByText(SECOND_ELEM_HOVERED_TEXT)).not.toBeTruthy() 27 | expect(getByText(THIRD_ELEM_HOVERED_TEXT)).toBeTruthy() 28 | 29 | await $elems[0].moveTo() 30 | expect(getByText(FIRST_ELEM_HOVERED_TEXT)).toBeTruthy() 31 | expect(queryByText(SECOND_ELEM_HOVERED_TEXT)).not.toBeTruthy() 32 | expect(queryByText(THIRD_ELEM_HOVERED_TEXT)).not.toBeTruthy() 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /svelte-typescript-vite/src/tests/Longpress.test.ts: -------------------------------------------------------------------------------- 1 | import { $, browser, expect } from '@wdio/globals' 2 | import { render } from '@testing-library/svelte' 3 | import Longpress from '../lib/Longpress.svelte' 4 | 5 | describe('Svelte Component Testing', () => { 6 | it('increments value on click', async () => { 7 | const { getByText } = render(Longpress) 8 | const $btn = await $(getByText('press and hold')) 9 | 10 | await expect($('p')).not.toBeExisting() 11 | await browser.action('pointer') 12 | .move({ origin: $btn }) 13 | .down() 14 | .pause(2500) 15 | .up() 16 | .perform() 17 | 18 | await expect($('p')).toBeExisting() 19 | 20 | await browser.action('pointer') 21 | .move({ x: 0, y: 0 }) // move out 22 | .move({ origin: $btn }) // move back in 23 | .perform() 24 | await expect($('p')).not.toBeExisting() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /svelte-typescript-vite/src/tests/__fixtures__/Hoverable.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 |
7 | {#if active} 8 |

1st element is being hovered upon.

9 | {:else} 10 |

Hover over me!

11 | {/if} 12 |
13 |
14 | 15 | 16 |
17 | {#if active} 18 |

2nd element is being hovered upon.

19 | {:else} 20 |

Hover over me!

21 | {/if} 22 |
23 |
24 | 25 | 26 |
27 | {#if active} 28 |

3rd element is being hovered upon.

29 | {:else} 30 |

Hover over me!

31 | {/if} 32 |
33 |
34 | 35 | 51 | -------------------------------------------------------------------------------- /svelte-typescript-vite/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /svelte-typescript-vite/svelte.config.js: -------------------------------------------------------------------------------- 1 | import sveltePreprocess from 'svelte-preprocess' 2 | 3 | export default { 4 | // Consult https://github.com/sveltejs/svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: sveltePreprocess() 7 | } 8 | -------------------------------------------------------------------------------- /svelte-typescript-vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "resolveJsonModule": true, 8 | /** 9 | * Typecheck JS in `.svelte` and `.js` files by default. 10 | * Disable checkJs if you'd like to use dynamic types in JS. 11 | * Note that setting allowJs false does not prevent the use 12 | * of JS in `.svelte` files. 13 | */ 14 | "allowJs": true, 15 | "checkJs": true, 16 | "ignoreDeprecations": "5.0", 17 | "verbatimModuleSyntax": false 18 | }, 19 | "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte", "tests/**/*.svelte"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /svelte-typescript-vite/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /svelte-typescript-vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { svelte } from '@sveltejs/vite-plugin-svelte' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [svelte()] 7 | }) 8 | -------------------------------------------------------------------------------- /vue-typescript-vite/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /vue-typescript-vite/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /vue-typescript-vite/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + TypeScript + Vite 2 | 3 | This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` 12 | 13 | 14 | -------------------------------------------------------------------------------- /vue-typescript-vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-typescript-vite", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vue-tsc && vite build", 9 | "preview": "vite preview", 10 | "wdio": "wdio run ./wdio.conf.ts" 11 | }, 12 | "dependencies": { 13 | "vue": "^3.5.13" 14 | }, 15 | "devDependencies": { 16 | "@testing-library/vue": "^8.1.0", 17 | "@types/mocha": "^10.0.10", 18 | "@vitejs/plugin-vue": "^5.2.1", 19 | "@vue/compiler-dom": "^3.5.13", 20 | "@wdio/browser-runner": "^9.7.3", 21 | "@wdio/cli": "^9.7.3", 22 | "@wdio/mocha-framework": "^9.7.3", 23 | "@wdio/spec-reporter": "^9.6.3", 24 | "ts-node": "^10.9.2", 25 | "typescript": "^5.7.3", 26 | "vite": "^6.1.0", 27 | "vue-tsc": "^2.2.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /vue-typescript-vite/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue-typescript-vite/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 32 | -------------------------------------------------------------------------------- /vue-typescript-vite/src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue-typescript-vite/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 33 | 34 | 39 | -------------------------------------------------------------------------------- /vue-typescript-vite/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import './style.css' 3 | import App from './App.vue' 4 | 5 | createApp(App).mount('#app') 6 | -------------------------------------------------------------------------------- /vue-typescript-vite/src/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color-scheme: light dark; 8 | color: rgba(255, 255, 255, 0.87); 9 | background-color: #242424; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | a { 19 | font-weight: 500; 20 | color: #646cff; 21 | text-decoration: inherit; 22 | } 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | body { 28 | margin: 0; 29 | display: flex; 30 | place-items: center; 31 | min-width: 320px; 32 | min-height: 100vh; 33 | } 34 | 35 | h1 { 36 | font-size: 3.2em; 37 | line-height: 1.1; 38 | } 39 | 40 | button { 41 | border-radius: 8px; 42 | border: 1px solid transparent; 43 | padding: 0.6em 1.2em; 44 | font-size: 1em; 45 | font-weight: 500; 46 | font-family: inherit; 47 | background-color: #1a1a1a; 48 | cursor: pointer; 49 | transition: border-color 0.25s; 50 | } 51 | button:hover { 52 | border-color: #646cff; 53 | } 54 | button:focus, 55 | button:focus-visible { 56 | outline: 4px auto -webkit-focus-ring-color; 57 | } 58 | 59 | .card { 60 | padding: 2em; 61 | } 62 | 63 | #app { 64 | max-width: 1280px; 65 | margin: 0 auto; 66 | padding: 2rem; 67 | text-align: center; 68 | } 69 | 70 | @media (prefers-color-scheme: light) { 71 | :root { 72 | color: #213547; 73 | background-color: #ffffff; 74 | } 75 | a:hover { 76 | color: #747bff; 77 | } 78 | button { 79 | background-color: #f9f9f9; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /vue-typescript-vite/src/tests/HelloWorld.test.ts: -------------------------------------------------------------------------------- 1 | import { $, expect } from '@wdio/globals' 2 | import { render } from '@testing-library/vue' 3 | import HelloWorld from '../components/HelloWorld.vue' 4 | 5 | describe('Vue Component Testing', () => { 6 | it('increments value on click', async () => { 7 | // The render method returns a collection of utilities to query your component. 8 | const { getByText } = render(HelloWorld) 9 | 10 | // getByText returns the first matching node for the provided text, and 11 | // throws an error if no elements match or if more than one match is found. 12 | const btn = getByText('count is 0') 13 | const button = await $(btn) 14 | 15 | // Dispatch a native click event to our button element. 16 | await button.click() 17 | await button.click() 18 | 19 | getByText('count is 2') 20 | await expect($('button=count is 2')).toExist() 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /vue-typescript-vite/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | const component: DefineComponent<{}, {}, any> 6 | export default component 7 | } 8 | -------------------------------------------------------------------------------- /vue-typescript-vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "resolveJsonModule": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "lib": ["ESNext", "DOM"], 13 | "skipLibCheck": true, 14 | "noEmit": true, 15 | "ignoreDeprecations": "5.0" 16 | }, 17 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], 18 | "references": [{ "path": "./tsconfig.node.json" }] 19 | } 20 | -------------------------------------------------------------------------------- /vue-typescript-vite/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /vue-typescript-vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()] 7 | }) 8 | --------------------------------------------------------------------------------