mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-28 17:18:46 +00:00
Add E2E tests using Cypress
This commit is contained in:
@@ -6,7 +6,7 @@ module.exports = {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"tsconfigRootDir": __dirname,
|
||||
"project": "./tsconfig.json"
|
||||
"project": "./tsconfig-eslint.json"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
@@ -14,8 +14,7 @@ module.exports = {
|
||||
"ignorePatterns": [
|
||||
"**/node_modules",
|
||||
"**/dist",
|
||||
".eslintrc.js",
|
||||
"vite.config.js"
|
||||
".eslintrc.js"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
|
||||
3
cypress.json
Normal file
3
cypress.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:3000"
|
||||
}
|
||||
1
cypress/fixtures/example.json
Normal file
1
cypress/fixtures/example.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
64
cypress/integration/generators.spec.ts
Normal file
64
cypress/integration/generators.spec.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
describe('Generators', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/loot-table/')
|
||||
cy.contains('h1', 'Loot Table Generator').should('exist')
|
||||
cy.contains('label', 'Pools').should('exist')
|
||||
})
|
||||
|
||||
it('can change an input field', () => {
|
||||
cy.get('label:contains("Rolls") ~ input').clear().type('4').blur()
|
||||
cy.get('[data-cy=import-area]').should('contain.value', '"rolls": 4')
|
||||
})
|
||||
|
||||
it('can change a dropdown', () => {
|
||||
cy.get('label:contains("Type") ~ select').first().select('minecraft:entity')
|
||||
cy.get('[data-cy=import-area]').should('contain.value', '"type": "minecraft:entity"')
|
||||
})
|
||||
|
||||
it('can add an element to an array', () => {
|
||||
cy.get('label:contains("Pools") ~ button').click()
|
||||
cy.get('[data-cy=tree]')
|
||||
.find('label:contains("Rolls")').should('have.length', 2)
|
||||
})
|
||||
|
||||
it('can remove an element from an array', () => {
|
||||
cy.contains('label', 'Pools')
|
||||
.get('button[aria-label="Remove"]').first().click()
|
||||
cy.get('label:contains("Rolls")').should('not.exist')
|
||||
})
|
||||
|
||||
it('can change a boolean field', () => {
|
||||
cy.get('label:contains("Functions") ~ button').first().click()
|
||||
cy.get('label:contains("Function") ~ select').should('have.value', 'minecraft:set_count')
|
||||
cy.get('label:contains("Add") ~ button:contains("False")').click()
|
||||
cy.get('[data-cy=import-area]').should('contain.value', '"add": false')
|
||||
cy.get('label:contains("Add") ~ button:contains("False")').click()
|
||||
cy.get('[data-cy=import-area]').should('not.contain.value', '"add": false')
|
||||
cy.get('label:contains("Add") ~ button:contains("True")').click()
|
||||
cy.get('[data-cy=import-area]').should('contain.value', '"add": true')
|
||||
})
|
||||
|
||||
it('can collapse an element', () => {
|
||||
cy.contains('label', 'Pools')
|
||||
.get('button[aria-label^="Collapse"]').first().click()
|
||||
cy.get('label:contains("Rolls")').should('not.exist')
|
||||
cy.contains('label', 'Pools')
|
||||
.get('button[aria-label^="Expand"]').first().click()
|
||||
cy.get('[data-cy=tree]')
|
||||
.find('label:contains("Rolls")').should('have.length', 1)
|
||||
})
|
||||
|
||||
it('can move elements in an array', () => {
|
||||
cy.get('label:contains("Rolls") ~ input').clear().type('4').blur()
|
||||
cy.get('[data-cy=import-area]').should('contain.value', '"rolls": 4')
|
||||
cy.get('label:contains("Rolls") ~ input').first().should('have.value', '4')
|
||||
cy.get('label:contains("Pools") ~ button').click()
|
||||
cy.get('[data-cy=tree]')
|
||||
.find('label:contains("Rolls")').should('have.length', 2)
|
||||
cy.get('label:contains("Rolls") ~ input').first().should('have.value', '1')
|
||||
cy.get('button[aria-label="Move down"]').first().click()
|
||||
cy.get('label:contains("Rolls") ~ input').first().should('have.value', '4')
|
||||
cy.get('button[aria-label="Move up"]').last().click()
|
||||
cy.get('label:contains("Rolls") ~ input').first().should('have.value', '1')
|
||||
})
|
||||
})
|
||||
86
cypress/integration/main.spec.ts
Normal file
86
cypress/integration/main.spec.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
describe('Settings and navigation', () => {
|
||||
|
||||
describe('Homepage', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/')
|
||||
cy.contains('h1', 'Data Pack Generators').should('exist')
|
||||
})
|
||||
|
||||
it('can switch themes', () => {
|
||||
cy.get('[data-cy=theme-switcher]').click()
|
||||
.contains('Light').click()
|
||||
cy.get('html').invoke('attr', 'data-theme').should('eq', 'light')
|
||||
})
|
||||
|
||||
it('can switch languages', () => {
|
||||
cy.get('[data-cy=language-switcher]').click()
|
||||
.contains('Deutsch').click()
|
||||
cy.contains('h1', 'Datenpaketgeneratoren').should('exist')
|
||||
})
|
||||
|
||||
it('can open a generator', () => {
|
||||
cy.contains('Loot Table').click()
|
||||
cy.url().should('contain', '/loot-table')
|
||||
cy.contains('h1', 'Loot Table Generator').should('exist')
|
||||
cy.contains('label', 'Pools').should('exist')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('Generators', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/loot-table/')
|
||||
cy.contains('h1', 'Loot Table Generator').should('exist')
|
||||
cy.contains('label', 'Pools').should('exist')
|
||||
})
|
||||
|
||||
it('can switch between generators', () => {
|
||||
cy.get('[data-cy=generator-switcher]').click()
|
||||
.contains('Predicate').click()
|
||||
cy.url().should('contain', '/predicate')
|
||||
cy.contains('h1', 'Predicate Generator').should('exist')
|
||||
})
|
||||
|
||||
it('can switch schema versions', () => {
|
||||
cy.contains('Constant').should('exist')
|
||||
cy.contains('Exact').should('not.exist')
|
||||
cy.get('[data-cy=version-switcher]').click()
|
||||
.contains('1.16').click()
|
||||
cy.contains('Exact').should('exist')
|
||||
cy.contains('Constant').should('not.exist')
|
||||
})
|
||||
|
||||
it('can load a preset', () => {
|
||||
cy.contains('Presets').click()
|
||||
cy.contains('blocks/acacia_log').click()
|
||||
cy.get('label:contains("Name") ~ input').should('have.value', 'minecraft:acacia_log')
|
||||
})
|
||||
|
||||
it('can import JSON', () => {
|
||||
cy.contains('Import').click()
|
||||
cy.get('[data-cy=import-area]')
|
||||
.type('{"pools":[{"rolls":2,"entries":[{"type":"minecraft:item","name":"minecraft:diamond"}]}]}', { parseSpecialCharSequences: false })
|
||||
.blur()
|
||||
cy.get('label:contains("Name") ~ input').should('have.value', 'minecraft:diamond')
|
||||
cy.get('label:contains("Rolls") ~ input').should('have.value', '2')
|
||||
})
|
||||
|
||||
it('can output YAML', () => {
|
||||
cy.get('[data-cy=source-controls]').click()
|
||||
.contains('YAML').click()
|
||||
cy.get('[data-cy=import-area]').should('have.value', "pools:\n - rolls: 1\n entries:\n - type: 'minecraft:item'\n name: 'minecraft:stone'\n")
|
||||
})
|
||||
|
||||
it('can output minified', () => {
|
||||
cy.get('[data-cy=source-controls]').click()
|
||||
.contains('Minified').click()
|
||||
cy.get('[data-cy=import-area]').should('have.value', '{"pools":[{"rolls":1,"entries":[{"type":"minecraft:item","name":"minecraft:stone"}]}]}\n')
|
||||
})
|
||||
|
||||
it('can go back to the homepage', () => {
|
||||
cy.get('[data-cy=home-link]').click()
|
||||
cy.url().should('not.contain', '/loot-table')
|
||||
cy.contains('h1', 'Data Pack Generators').should('exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
2
cypress/plugins/index.ts
Normal file
2
cypress/plugins/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export default (on, config) => {
|
||||
}
|
||||
0
cypress/support/index.ts
Normal file
0
cypress/support/index.ts
Normal file
8
cypress/tsconfig.json
Normal file
8
cypress/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"types": ["cypress"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
2752
package-lock.json
generated
2752
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -40,6 +40,7 @@
|
||||
"@types/seedrandom": "^2.4.28",
|
||||
"@typescript-eslint/eslint-plugin": "^4.25.0",
|
||||
"@typescript-eslint/parser": "^4.25.0",
|
||||
"cypress": "^9.2.0",
|
||||
"eslint": "^7.27.0",
|
||||
"preact": "^10.5.13",
|
||||
"preact-router": "^3.2.1",
|
||||
|
||||
@@ -3,17 +3,18 @@ import type { Octicon } from '.'
|
||||
import { Btn } from '.'
|
||||
import { useFocus } from '../hooks'
|
||||
|
||||
type BtnMenuProps = {
|
||||
interface BtnMenuProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||
icon?: keyof typeof Octicon,
|
||||
label?: string,
|
||||
relative?: boolean,
|
||||
tooltip?: string,
|
||||
children: ComponentChildren,
|
||||
}
|
||||
export function BtnMenu({ icon, label, relative, tooltip, children }: BtnMenuProps) {
|
||||
export function BtnMenu(props: BtnMenuProps) {
|
||||
const { icon, label, relative, tooltip, children } = props
|
||||
const [active, setActive] = useFocus()
|
||||
|
||||
return <div class={`btn-menu${relative === false ? ' no-relative' : ''}`}>
|
||||
return <div class={`btn-menu${relative === false ? ' no-relative' : ''}`} {...props}>
|
||||
<Btn {...{icon, label, tooltip}} onClick={setActive} />
|
||||
{active && <div class="btn-group">
|
||||
{children}
|
||||
|
||||
@@ -27,9 +27,9 @@ export function Header({ lang, title, version, theme, changeTheme, language, cha
|
||||
|
||||
return <header>
|
||||
<div class="title">
|
||||
<Link class="home-link" href="/" aria-label={loc('home')}>{Icons.home}</Link>
|
||||
<Link class="home-link" href="/" aria-label={loc('home')} data-cy="home-link">{Icons.home}</Link>
|
||||
<h1>{title}</h1>
|
||||
{gen && <BtnMenu icon="chevron_down" tooltip={loc('switch_generator')}>
|
||||
{gen && <BtnMenu icon="chevron_down" tooltip={loc('switch_generator')} data-cy="generator-switcher">
|
||||
{config.generators
|
||||
.filter(g => g.category === gen?.category && checkVersion(version, g.minVersion))
|
||||
.map(g =>
|
||||
@@ -39,7 +39,7 @@ export function Header({ lang, title, version, theme, changeTheme, language, cha
|
||||
</div>
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<li data-cy="language-switcher">
|
||||
<BtnMenu icon="globe" tooltip={loc('language')}>
|
||||
{config.languages.map(({ code, name }) =>
|
||||
<Btn label={name} active={code === language}
|
||||
@@ -47,7 +47,7 @@ export function Header({ lang, title, version, theme, changeTheme, language, cha
|
||||
)}
|
||||
</BtnMenu>
|
||||
</li>
|
||||
<li>
|
||||
<li data-cy="theme-switcher">
|
||||
<BtnMenu icon={Themes[theme]} tooltip={loc('theme')}>
|
||||
{Object.entries(Themes).map(([th, icon]) =>
|
||||
<Btn icon={icon} label={loc(`theme.${th}`)} active={th === theme}
|
||||
|
||||
@@ -25,7 +25,7 @@ const FORMATS: Record<string, {
|
||||
}> = {
|
||||
json: {
|
||||
parse: json.parse,
|
||||
stringify: (v, i) => json.stringify(v, null, i),
|
||||
stringify: (v, i) => json.stringify(v, null, i) + '\n',
|
||||
},
|
||||
yaml: {
|
||||
parse: yaml.load,
|
||||
@@ -57,7 +57,7 @@ export function SourcePanel({ lang, name, model, blockStates, doCopy, doDownload
|
||||
|
||||
const getOutput = useCallback((model: DataModel, blockStates: BlockStateRegistry) => {
|
||||
const data = model.schema.hook(transformOutput, new ModelPath(model), model.data, { blockStates })
|
||||
return FORMATS[format].stringify(data, INDENT[indent]) + '\n'
|
||||
return FORMATS[format].stringify(data, INDENT[indent])
|
||||
}, [indent, format])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -136,7 +136,7 @@ export function SourcePanel({ lang, name, model, blockStates, doCopy, doDownload
|
||||
|
||||
return <>
|
||||
<div class="controls">
|
||||
<BtnMenu icon="gear" tooltip={loc('output_settings')}>
|
||||
<BtnMenu icon="gear" tooltip={loc('output_settings')} data-cy="source-controls">
|
||||
{Object.entries(INDENT).map(([key]) =>
|
||||
<Btn label={loc(`indentation.${key}`)} active={indent === key}
|
||||
onClick={() => changeIndent(key)}/>
|
||||
@@ -147,7 +147,7 @@ export function SourcePanel({ lang, name, model, blockStates, doCopy, doDownload
|
||||
onClick={() => changeFormat(key)} />)}
|
||||
</BtnMenu>
|
||||
</div>
|
||||
<textarea ref={source} class="source" onBlur={onImport} spellcheck={false} autocorrect="off" placeholder={loc('source_placeholder')}></textarea>
|
||||
<textarea ref={source} class="source" onBlur={onImport} spellcheck={false} autocorrect="off" placeholder={loc('source_placeholder')} data-cy="import-area"></textarea>
|
||||
<a ref={download} style="display: none;"></a>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export function Tree({ lang, version, model, blockStates, onError }: TreePanelPr
|
||||
setState(state => state + 1)
|
||||
})
|
||||
|
||||
return <div class="tree">
|
||||
return <div class="tree" data-cy="tree">
|
||||
<FullNode {...{model, lang, version, blockStates}}/>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ export function Generator({ lang, changeTitle, version, changeVersion }: Generat
|
||||
</div>
|
||||
{presetResults.length === 0 && <Btn label={loc('no_presets')}/>}
|
||||
</BtnMenu>
|
||||
<BtnMenu icon="tag" label={version}>
|
||||
<BtnMenu icon="tag" label={version} data-cy="version-switcher">
|
||||
{allowedVersions.reverse().map(v =>
|
||||
<Btn label={v} active={v === version} onClick={() => changeVersion(v)} />
|
||||
)}
|
||||
|
||||
8
tsconfig-eslint.json
Normal file
8
tsconfig-eslint.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
"./src",
|
||||
"./cypress",
|
||||
"vite.config.js"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user