mirror of
https://github.com/pestphp/pest.git
synced 2026-07-24 18:40:03 +02:00
Compare commits
31 Commits
b5fed82883
...
5.x
| Author | SHA1 | Date | |
|---|---|---|---|
| 759ac63139 | |||
| 333f469efb | |||
| c4ec925aa5 | |||
| 5c8cfed483 | |||
| 962510b7e3 | |||
| e9c268a584 | |||
| fa91fcf744 | |||
| c6073511a2 | |||
| b5e1b83bb4 | |||
| 1c11595504 | |||
| cd0e921158 | |||
| f8c4c061f8 | |||
| 81eacd79fd | |||
| 1ef680c75d | |||
| 820fa08313 | |||
| 9e79e491e2 | |||
| 1b83b8fcda | |||
| ff4aba2ffa | |||
| feaac1ab7e | |||
| bbc6ada21e | |||
| 3927dbfcdf | |||
| b52bbb4cca | |||
| 52819501eb | |||
| 7b18287374 | |||
| 33e3bf2888 | |||
| 72ab506d7b | |||
| 5dc49a71d6 | |||
| 1adb484d3f | |||
| aadf7e92a6 | |||
| df42b9ed7b | |||
| 632ae79c8b |
@@ -28,7 +28,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
|
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
|
||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Cache Composer dependencies
|
- name: Cache Composer dependencies
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.composer-cache.outputs.dir }}
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
key: static-php-8.4-${{ matrix.dependency-version }}-composer-${{ hashFiles('**/composer.json', '**/composer.lock') }}
|
key: static-php-8.4-${{ matrix.dependency-version }}-composer-${{ hashFiles('**/composer.json', '**/composer.lock') }}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
|
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
|
||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Cache Composer dependencies
|
- name: Cache Composer dependencies
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.composer-cache.outputs.dir }}
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
key: ${{ matrix.os }}-php-${{ matrix.php }}-symfony-${{ matrix.symfony }}-composer-${{ hashFiles('**/composer.json', '**/composer.lock') }}
|
key: ${{ matrix.os }}-php-${{ matrix.php }}-symfony-${{ matrix.symfony }}-composer-${{ hashFiles('**/composer.json', '**/composer.lock') }}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
> Pest v5 Now Available: **[Read the announcement »](https://pestphp.com/docs/pest-v5-is-here)**.
|
> Pest v5 Now Available: **[Read the announcement »](https://pestphp.com/docs/pest-v5-is-here)**.
|
||||||
|
|
||||||
**Pest** is an elegant PHP testing Framework with a focus on simplicity, meticulously designed to bring back the joy of testing in PHP.
|
**Pest** is an elegant testing framework for PHP developers and AI agents.
|
||||||
|
|
||||||
- Explore our docs at **[pestphp.com »](https://pestphp.com)**
|
- Explore our docs at **[pestphp.com »](https://pestphp.com)**
|
||||||
- Follow the creator Nuno Maduro:
|
- Follow the creator Nuno Maduro:
|
||||||
@@ -29,26 +29,12 @@
|
|||||||
|
|
||||||
We cannot thank our sponsors enough for their incredible support in funding Pest's development. Their contributions have been instrumental in making Pest the best it can be. For those who are interested in becoming a sponsor, please visit Nuno Maduro's Sponsor page at **[github.com/sponsors/nunomaduro](https://github.com/sponsors/nunomaduro)**.
|
We cannot thank our sponsors enough for their incredible support in funding Pest's development. Their contributions have been instrumental in making Pest the best it can be. For those who are interested in becoming a sponsor, please visit Nuno Maduro's Sponsor page at **[github.com/sponsors/nunomaduro](https://github.com/sponsors/nunomaduro)**.
|
||||||
|
|
||||||
|
|
||||||
### Platinum Sponsors
|
|
||||||
|
|
||||||
- **[CodeRabbit](https://coderabbit.ai/?ref=pestphp)**
|
|
||||||
- **[Mailtrap](https://l.rw.rw/pestphp)**
|
|
||||||
- **[SerpApi](https://serpapi.com/?ref=nunomaduro)**
|
|
||||||
- **[Tighten](https://tighten.com/?ref=nunomaduro)**
|
|
||||||
- **[Redberry](https://redberry.international/laravel-development/?utm_source=pest&utm_medium=banner&utm_campaign=pest_sponsorship)**
|
|
||||||
|
|
||||||
### Gold Sponsors
|
|
||||||
|
|
||||||
- **[CMS Max](https://cmsmax.com/?ref=pestphp)**
|
- **[CMS Max](https://cmsmax.com/?ref=pestphp)**
|
||||||
|
- **[PhpStorm](https://jb.gg/nuno)**
|
||||||
### Premium Sponsors
|
- **[CodeRabbit](https://coderabbit.link/nunomaduro)**
|
||||||
|
- **[SerpApi](https://serpapi.com/?ref=nunomaduro)**
|
||||||
- [Zapiet](https://zapiet.com/?ref=pestphp)
|
- **[Typesense](https://typesense.org/?ref=nunomaduro)**
|
||||||
- [Load Forge](https://loadforge.com/?ref=pestphp)
|
- **[Bento](https://bentonow.com/?ref=nunomaduro)**
|
||||||
- [Route4Me](https://route4me.com/pt?ref=pestphp)
|
- **[Redberry](https://redberry.international/laravel-development/)**
|
||||||
- [Nerdify](https://getnerdify.com/?ref=pestphp)
|
|
||||||
- [Akaunting](https://akaunting.com/?ref=pestphp)
|
|
||||||
- [TestMu AI](https://www.testmuai.com/?utm_medium=sponsor&utm_source=pest)
|
|
||||||
|
|
||||||
Pest is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
|
Pest is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
|
||||||
|
|||||||
+149
-14
@@ -25,26 +25,72 @@ const PAGE_DIR_CANDIDATES = [
|
|||||||
|
|
||||||
async function loadRolldown() {
|
async function loadRolldown() {
|
||||||
const projectRequire = createRequire(join(PROJECT_ROOT, 'package.json'))
|
const projectRequire = createRequire(join(PROJECT_ROOT, 'package.json'))
|
||||||
const path = projectRequire.resolve('rolldown')
|
let path = null
|
||||||
|
|
||||||
|
try { path = projectRequire.resolve('rolldown') } catch {}
|
||||||
|
|
||||||
|
if (path === null) {
|
||||||
|
// rolldown-vite installs (vite@npm:rolldown-vite) ship rolldown as a
|
||||||
|
// dependency of the vite package rather than a top-level install.
|
||||||
|
try {
|
||||||
|
const viteRequire = createRequire(projectRequire.resolve('vite/package.json'))
|
||||||
|
path = viteRequire.resolve('rolldown')
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path === null) return null
|
||||||
|
|
||||||
return await import(pathToFileURL(path).href)
|
return await import(pathToFileURL(path).href)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stripJsonComments(raw) {
|
||||||
|
let out = ''
|
||||||
|
let inString = false
|
||||||
|
let quote = ''
|
||||||
|
let inLine = false
|
||||||
|
let inBlock = false
|
||||||
|
|
||||||
|
for (let i = 0; i < raw.length; i++) {
|
||||||
|
const c = raw[i]
|
||||||
|
const n = raw[i + 1]
|
||||||
|
|
||||||
|
if (inLine) {
|
||||||
|
if (c === '\n') { inLine = false; out += c }
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (inBlock) {
|
||||||
|
if (c === '*' && n === '/') { inBlock = false; i++ }
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (inString) {
|
||||||
|
out += c
|
||||||
|
if (c === '\\') { out += n ?? ''; i++; continue }
|
||||||
|
if (c === quote) inString = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (c === '"' || c === "'") { inString = true; quote = c; out += c; continue }
|
||||||
|
if (c === '/' && n === '/') { inLine = true; i++; continue }
|
||||||
|
if (c === '/' && n === '*') { inBlock = true; i++; continue }
|
||||||
|
if (c === '}' || c === ']') out = out.replace(/,\s*$/, '')
|
||||||
|
out += c
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
async function readJsonWithComments(path) {
|
async function readJsonWithComments(path) {
|
||||||
const raw = await readFile(path, 'utf8')
|
const raw = await readFile(path, 'utf8')
|
||||||
const stripped = raw
|
return JSON.parse(stripJsonComments(raw))
|
||||||
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
||||||
.replace(/(^|[^:])\/\/[^\n]*/g, '$1')
|
|
||||||
return JSON.parse(stripped)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadAliasFromTsconfig() {
|
export async function loadAliasFromTsconfig(projectRoot = PROJECT_ROOT) {
|
||||||
const alias = {}
|
const alias = {}
|
||||||
for (const name of ['tsconfig.json', 'jsconfig.json']) {
|
for (const name of ['tsconfig.json', 'jsconfig.json']) {
|
||||||
const p = join(PROJECT_ROOT, name)
|
const p = join(projectRoot, name)
|
||||||
if (!existsSync(p)) continue
|
if (!existsSync(p)) continue
|
||||||
let cfg
|
let cfg
|
||||||
try { cfg = await readJsonWithComments(p) } catch { continue }
|
try { cfg = await readJsonWithComments(p) } catch { continue }
|
||||||
const baseUrl = resolve(PROJECT_ROOT, cfg?.compilerOptions?.baseUrl ?? '.')
|
const baseUrl = resolve(projectRoot, cfg?.compilerOptions?.baseUrl ?? '.')
|
||||||
const paths = cfg?.compilerOptions?.paths ?? {}
|
const paths = cfg?.compilerOptions?.paths ?? {}
|
||||||
for (const [key, targets] of Object.entries(paths)) {
|
for (const [key, targets] of Object.entries(paths)) {
|
||||||
if (!key.endsWith('/*')) continue
|
if (!key.endsWith('/*')) continue
|
||||||
@@ -58,6 +104,74 @@ async function loadAliasFromTsconfig() {
|
|||||||
return alias
|
return alias
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VITE_CONFIG_FILES = [
|
||||||
|
'vite.config.ts',
|
||||||
|
'vite.config.js',
|
||||||
|
'vite.config.mjs',
|
||||||
|
'vite.config.cjs',
|
||||||
|
'vite.config.mts',
|
||||||
|
'vite.config.cts',
|
||||||
|
]
|
||||||
|
|
||||||
|
function resolveAliasTarget(projectRoot, target) {
|
||||||
|
if (target.startsWith('/')) {
|
||||||
|
// Vite resolves a leading slash against the project root, unless the
|
||||||
|
// config author used a genuinely absolute path.
|
||||||
|
return existsSync(target) ? target : resolve(projectRoot, '.' + target)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(projectRoot, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function usesLaravelVitePlugin(projectRoot) {
|
||||||
|
const p = join(projectRoot, 'package.json')
|
||||||
|
if (!existsSync(p)) return false
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pkg = JSON.parse(await readFile(p, 'utf8'))
|
||||||
|
return Boolean(pkg.dependencies?.['laravel-vite-plugin'] ?? pkg.devDependencies?.['laravel-vite-plugin'])
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadAliasFromViteConfig(projectRoot = PROJECT_ROOT) {
|
||||||
|
const alias = {}
|
||||||
|
let source = null
|
||||||
|
|
||||||
|
for (const name of VITE_CONFIG_FILES) {
|
||||||
|
const p = join(projectRoot, name)
|
||||||
|
if (!existsSync(p)) continue
|
||||||
|
try { source = await readFile(p, 'utf8') } catch { continue }
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source !== null) {
|
||||||
|
// The config is executable code we cannot import safely, so extract alias
|
||||||
|
// entries textually: a quoted `@…`/`~…` key, then the last quoted path in
|
||||||
|
// the value expression — covers `'@': '/resources/js'` as well as
|
||||||
|
// `'@': path.resolve(__dirname, 'resources/js')`. The value stops at a
|
||||||
|
// top-level comma; call arguments are kept via the paren group.
|
||||||
|
for (const m of source.matchAll(/['"`]([@~][\w./-]*)['"`]\s*:\s*((?:\([^)\n]*\)|[^,\n])*)/g)) {
|
||||||
|
const key = m[1]
|
||||||
|
if (alias[key] !== undefined) continue
|
||||||
|
|
||||||
|
const paths = [...m[2].matchAll(/['"`]([^'"`]+)['"`]/g)].map((p) => p[1])
|
||||||
|
const target = paths.length > 0 ? paths[paths.length - 1] : null
|
||||||
|
if (!target || target.includes('*')) continue
|
||||||
|
|
||||||
|
alias[key] = resolveAliasTarget(projectRoot, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// laravel-vite-plugin registers '@' → resources/js by default.
|
||||||
|
if (alias['@'] === undefined && (await usesLaravelVitePlugin(projectRoot))) {
|
||||||
|
alias['@'] = resolve(projectRoot, 'resources/js')
|
||||||
|
}
|
||||||
|
|
||||||
|
return alias
|
||||||
|
}
|
||||||
|
|
||||||
async function listPageFiles(pagesDir) {
|
async function listPageFiles(pagesDir) {
|
||||||
if (!existsSync(pagesDir)) return []
|
if (!existsSync(pagesDir)) return []
|
||||||
|
|
||||||
@@ -121,8 +235,16 @@ async function main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { rolldown } = await loadRolldown()
|
const loaded = await loadRolldown()
|
||||||
const alias = await loadAliasFromTsconfig()
|
|
||||||
|
if (loaded === null) {
|
||||||
|
process.stdout.write('{}')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { rolldown } = loaded
|
||||||
|
// The vite config is what the dev server actually resolves with — let it win.
|
||||||
|
const alias = { ...(await loadAliasFromTsconfig()), ...(await loadAliasFromViteConfig()) }
|
||||||
const aliasKeys = Object.keys(alias)
|
const aliasKeys = Object.keys(alias)
|
||||||
|
|
||||||
const graph = new Map()
|
const graph = new Map()
|
||||||
@@ -167,7 +289,7 @@ async function main() {
|
|||||||
cwd: PROJECT_ROOT,
|
cwd: PROJECT_ROOT,
|
||||||
resolve: {
|
resolve: {
|
||||||
alias,
|
alias,
|
||||||
extensions: ['.tsx', '.ts', '.jsx', '.js', '.mjs', '.cjs', '.json'],
|
extensions: ['.tsx', '.ts', '.jsx', '.js', '.mts', '.cts', '.mjs', '.cjs', '.json', '.vue', '.svelte'],
|
||||||
},
|
},
|
||||||
transform: { jsx: 'preserve' },
|
transform: { jsx: 'preserve' },
|
||||||
treeshake: false,
|
treeshake: false,
|
||||||
@@ -192,6 +314,11 @@ async function main() {
|
|||||||
|
|
||||||
stack.add(id)
|
stack.add(id)
|
||||||
const acc = new Set()
|
const acc = new Set()
|
||||||
|
// A set computed while skipping an in-stack (cyclic) dependency is missing
|
||||||
|
// that dependency's subtree and must not be memoized — the ancestor call
|
||||||
|
// completes it for the current traversal, but a cached copy would leak the
|
||||||
|
// incomplete set into other pages' traversals.
|
||||||
|
let complete = true
|
||||||
const deps = graph.get(id)
|
const deps = graph.get(id)
|
||||||
if (deps) {
|
if (deps) {
|
||||||
for (const dep of deps) {
|
for (const dep of deps) {
|
||||||
@@ -200,13 +327,17 @@ async function main() {
|
|||||||
const rel = relative(PROJECT_ROOT, dep).split(sep).join('/')
|
const rel = relative(PROJECT_ROOT, dep).split(sep).join('/')
|
||||||
acc.add(rel)
|
acc.add(rel)
|
||||||
}
|
}
|
||||||
if (stack.has(dep)) continue
|
if (stack.has(dep)) {
|
||||||
|
complete = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
const child = computeTransitive(dep, stack)
|
const child = computeTransitive(dep, stack)
|
||||||
if (child) for (const r of child) acc.add(r)
|
if (child) for (const r of child) acc.add(r)
|
||||||
|
if (!transitiveCache.has(dep)) complete = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stack.delete(id)
|
stack.delete(id)
|
||||||
transitiveCache.set(id, acc)
|
if (complete) transitiveCache.set(id, acc)
|
||||||
return acc
|
return acc
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,10 +361,14 @@ async function main() {
|
|||||||
process.stdout.write(JSON.stringify(payload))
|
process.stdout.write(JSON.stringify(payload))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const invokedDirectly = process.argv[1] !== undefined
|
||||||
|
&& import.meta.url === pathToFileURL(process.argv[1]).href
|
||||||
|
|
||||||
|
if (invokedDirectly) {
|
||||||
try {
|
try {
|
||||||
void pathToFileURL
|
|
||||||
await main()
|
await main()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
process.stderr.write(String(err?.stack ?? err ?? 'unknown error'))
|
process.stderr.write(String(err?.stack ?? err ?? 'unknown error'))
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
+8
-7
@@ -18,19 +18,19 @@
|
|||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.4",
|
"php": "^8.4",
|
||||||
"brianium/paratest": "^7.22.4",
|
"brianium/paratest": "^7.23.0",
|
||||||
"nunomaduro/collision": "^8.9.4",
|
"nunomaduro/collision": "^8.9.5",
|
||||||
"nunomaduro/termwind": "^2.4.0",
|
"nunomaduro/termwind": "^2.4.0",
|
||||||
"pestphp/pest-plugin": "^5.0.0",
|
"pestphp/pest-plugin": "^5.0.0",
|
||||||
"pestphp/pest-plugin-arch": "^5.0.0",
|
"pestphp/pest-plugin-arch": "^5.0.0",
|
||||||
"pestphp/pest-plugin-mutate": "^5.0.0",
|
"pestphp/pest-plugin-mutate": "^5.0.0",
|
||||||
"pestphp/pest-plugin-profanity": "^5.0.0",
|
"pestphp/pest-plugin-profanity": "^5.0.0",
|
||||||
"phpunit/phpunit": "^13.1.8",
|
"phpunit/phpunit": "^13.2.4",
|
||||||
"symfony/process": "^8.1.0"
|
"symfony/process": "^8.1.0"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"filp/whoops": "<2.18.3",
|
"filp/whoops": "<2.18.3",
|
||||||
"phpunit/phpunit": ">13.1.8",
|
"phpunit/phpunit": ">13.2.4",
|
||||||
"sebastian/exporter": "<7.0.0",
|
"sebastian/exporter": "<7.0.0",
|
||||||
"webmozart/assert": "<1.11.0"
|
"webmozart/assert": "<1.11.0"
|
||||||
},
|
},
|
||||||
@@ -58,12 +58,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"mrpunyapal/peststan": "^0.2.10",
|
"laravel/pao": "^1.1.2",
|
||||||
"laravel/pao": "^1.1.1",
|
|
||||||
"pestphp/pest-dev-tools": "^5.0.0",
|
"pestphp/pest-dev-tools": "^5.0.0",
|
||||||
"pestphp/pest-plugin-browser": "^5.0.0",
|
"pestphp/pest-plugin-browser": "^5.0.0",
|
||||||
|
"pestphp/pest-plugin-phpstan": "^5.0.0",
|
||||||
|
"pestphp/pest-plugin-rector": "^5.0.0",
|
||||||
"pestphp/pest-plugin-type-coverage": "^5.0.0",
|
"pestphp/pest-plugin-type-coverage": "^5.0.0",
|
||||||
"psy/psysh": "^0.12.23"
|
"psy/psysh": "^0.12.24"
|
||||||
},
|
},
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011 Brian Scaturro
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ParaTest\WrapperRunner;
|
||||||
|
|
||||||
|
use PHPUnit\TextUI\Output\Printer;
|
||||||
|
|
||||||
|
use function preg_match;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* This file is overridden so the "T" progress character — emitted by Pest for
|
||||||
|
* "todo" tests — is routed to the progress file next to the regular characters,
|
||||||
|
* instead of being treated as unexpected output.
|
||||||
|
*/
|
||||||
|
final readonly class ProgressPrinterOutput implements Printer
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private Printer $progressPrinter,
|
||||||
|
private Printer $outputPrinter,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function print(string $buffer): void
|
||||||
|
{
|
||||||
|
// Skip anything in \PHPUnit\TextUI\Output\Default\ProgressPrinter\ProgressPrinter::printProgress except $progress
|
||||||
|
if (
|
||||||
|
$buffer === "\n"
|
||||||
|
|| preg_match('/^ +$/', $buffer) === 1
|
||||||
|
|| preg_match('/^ \d+ \/ \d+ \(...%\)$/', $buffer) === 1
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match ($buffer) {
|
||||||
|
'E', 'F', 'I', 'N', 'D', 'R', 'W', 'S', 'T', '.' => $this->progressPrinter->print($buffer),
|
||||||
|
default => $this->outputPrinter->print($buffer),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function flush(): void
|
||||||
|
{
|
||||||
|
$this->progressPrinter->flush();
|
||||||
|
$this->outputPrinter->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-6
@@ -43,7 +43,7 @@ declare(strict_types=1);
|
|||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Pest\Logging\TeamCity\Subscriber;
|
namespace PHPUnit\TextUI\Output\Default\ProgressPrinter;
|
||||||
|
|
||||||
use PHPUnit\Event\Test\Skipped;
|
use PHPUnit\Event\Test\Skipped;
|
||||||
use PHPUnit\Event\Test\SkippedSubscriber;
|
use PHPUnit\Event\Test\SkippedSubscriber;
|
||||||
@@ -51,16 +51,20 @@ use ReflectionClass;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||||
|
*
|
||||||
|
* This file is overridden so PHPUnit's progress output emits a "T" before the
|
||||||
|
* regular "S" for "todo" tests — Pest's parallel result printer consumes the
|
||||||
|
* "T" and swallows the "S" that follows it.
|
||||||
*/
|
*/
|
||||||
final class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber
|
final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber
|
||||||
{
|
{
|
||||||
public function notify(Skipped $event): void
|
public function notify(Skipped $event): void
|
||||||
{
|
{
|
||||||
if (str_contains($event->message(), '__TODO__')) {
|
if ($event->message() === '__TODO__') {
|
||||||
$this->printTodoItem();
|
$this->printTodoItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->logger()->testSkipped($event);
|
$this->printer()->testSkipped();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,7 +73,7 @@ final class TestSkippedSubscriber extends Subscriber implements SkippedSubscribe
|
|||||||
private function printTodoItem(): void
|
private function printTodoItem(): void
|
||||||
{
|
{
|
||||||
$mirror = new ReflectionClass($this->printer());
|
$mirror = new ReflectionClass($this->printer());
|
||||||
$printerMirror = $mirror->getMethod('printProgress');
|
$printProgress = $mirror->getMethod('printProgress');
|
||||||
$printerMirror->invoke($this->printer(), 'T');
|
$printProgress->invoke($this->printer(), 'T');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
includes:
|
includes:
|
||||||
- phpstan-baseline.neon
|
- phpstan-baseline.neon
|
||||||
- phpstan-pest-extension.neon
|
- phpstan-pest-extension.neon
|
||||||
- vendor/mrpunyapal/peststan/extension.neon
|
- vendor/pestphp/pest-plugin-phpstan/extension.neon
|
||||||
|
|
||||||
parameters:
|
parameters:
|
||||||
level: 7
|
level: 7
|
||||||
|
|||||||
+38
@@ -2,22 +2,60 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Pest\Rector\Rules\UseToMatchArrayRector;
|
||||||
|
use Pest\Rector\Set\PestSetList;
|
||||||
use Rector\CodingStyle\Rector\ArrowFunction\ArrowFunctionDelegatingCallToFirstClassCallableRector;
|
use Rector\CodingStyle\Rector\ArrowFunction\ArrowFunctionDelegatingCallToFirstClassCallableRector;
|
||||||
use Rector\Config\RectorConfig;
|
use Rector\Config\RectorConfig;
|
||||||
|
use Rector\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector;
|
||||||
|
use Rector\DeadCode\Rector\ClassMethod\RemoveEmptyClassMethodRector;
|
||||||
use Rector\DeadCode\Rector\ClassMethod\RemoveParentDelegatingConstructorRector;
|
use Rector\DeadCode\Rector\ClassMethod\RemoveParentDelegatingConstructorRector;
|
||||||
|
use Rector\DeadCode\Rector\ClassMethod\RemoveReturnTagIncompatibleWithNativeTypeRector;
|
||||||
|
use Rector\DeadCode\Rector\ClassMethod\RemoveUnusedConstructorParamRector;
|
||||||
|
use Rector\DeadCode\Rector\ClassMethod\RemoveUselessUnionReturnDocblockRector;
|
||||||
|
use Rector\DeadCode\Rector\Property\RemoveUnusedPrivatePropertyRector;
|
||||||
|
use Rector\TypeDeclaration\Rector\ArrowFunction\AddArrowFunctionReturnTypeRector;
|
||||||
use Rector\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector;
|
use Rector\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector;
|
||||||
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector;
|
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector;
|
||||||
|
|
||||||
return RectorConfig::configure()
|
return RectorConfig::configure()
|
||||||
->withPaths([
|
->withPaths([
|
||||||
__DIR__.'/src',
|
__DIR__.'/src',
|
||||||
|
__DIR__.'/tests',
|
||||||
|
])
|
||||||
|
->withSets([
|
||||||
|
PestSetList::CODING_STYLE,
|
||||||
])
|
])
|
||||||
->withSkip([
|
->withSkip([
|
||||||
__DIR__.'/src/Plugins/Parallel/Paratest/WrapperRunner.php',
|
__DIR__.'/src/Plugins/Parallel/Paratest/WrapperRunner.php',
|
||||||
|
__DIR__.'/tests/Fixtures/Arch',
|
||||||
ReturnNeverTypeRector::class,
|
ReturnNeverTypeRector::class,
|
||||||
ArrowFunctionDelegatingCallToFirstClassCallableRector::class,
|
ArrowFunctionDelegatingCallToFirstClassCallableRector::class,
|
||||||
NarrowObjectReturnTypeRector::class,
|
NarrowObjectReturnTypeRector::class,
|
||||||
RemoveParentDelegatingConstructorRector::class,
|
RemoveParentDelegatingConstructorRector::class,
|
||||||
|
RemoveDuplicatedReturnSelfDocblockRector::class,
|
||||||
|
RemoveUselessUnionReturnDocblockRector::class,
|
||||||
|
RemoveReturnTagIncompatibleWithNativeTypeRector::class => [
|
||||||
|
__DIR__.'/src/Expectations/HigherOrderExpectation.php',
|
||||||
|
],
|
||||||
|
// Merges unrelated expectations into a single `toMatchArray()`, turning
|
||||||
|
// `toContain()` into exact matches, dropping `->not`, and mistaking a
|
||||||
|
// `toBeTrue()` failure message for an expected value. Unsafe here.
|
||||||
|
UseToMatchArrayRector::class,
|
||||||
|
// Test fixtures rely on "unused" constructors, params and properties
|
||||||
|
// (resolved via the container or read through reflection), so the
|
||||||
|
// dead-code and return-type rules below must not touch the test suite.
|
||||||
|
RemoveEmptyClassMethodRector::class => [
|
||||||
|
__DIR__.'/tests',
|
||||||
|
],
|
||||||
|
RemoveUnusedConstructorParamRector::class => [
|
||||||
|
__DIR__.'/tests',
|
||||||
|
],
|
||||||
|
RemoveUnusedPrivatePropertyRector::class => [
|
||||||
|
__DIR__.'/tests',
|
||||||
|
],
|
||||||
|
AddArrowFunctionReturnTypeRector::class => [
|
||||||
|
__DIR__.'/tests',
|
||||||
|
],
|
||||||
])
|
])
|
||||||
->withPreparedSets(
|
->withPreparedSets(
|
||||||
deadCode: true,
|
deadCode: true,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ final class BootOverrides implements Bootstrapper
|
|||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
public const array FILES = [
|
public const array FILES = [
|
||||||
|
'ParaTest/WrapperRunner/ProgressPrinterOutput.php',
|
||||||
'Runner/Filter/NameFilterIterator.php',
|
'Runner/Filter/NameFilterIterator.php',
|
||||||
'Runner/ResultCache/DefaultResultCache.php',
|
'Runner/ResultCache/DefaultResultCache.php',
|
||||||
'Runner/TestSuiteLoader.php',
|
'Runner/TestSuiteLoader.php',
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ use PHPUnit\Framework\Attributes\PostCondition;
|
|||||||
use PHPUnit\Framework\IncompleteTest;
|
use PHPUnit\Framework\IncompleteTest;
|
||||||
use PHPUnit\Framework\SkippedTest;
|
use PHPUnit\Framework\SkippedTest;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use PHPUnit\Framework\TestCase\ExceptionExpectation;
|
||||||
|
use PHPUnit\Framework\TestCase\OutputBuffer;
|
||||||
use ReflectionException;
|
use ReflectionException;
|
||||||
use ReflectionFunction;
|
use ReflectionFunction;
|
||||||
use ReflectionParameter;
|
use ReflectionParameter;
|
||||||
@@ -431,15 +433,15 @@ trait Testable
|
|||||||
unset($this->{$property});
|
unset($this->{$property});
|
||||||
}
|
}
|
||||||
|
|
||||||
$hasOutputExpectation = Closure::bind(fn (): bool => is_string($this->outputExpectedString) || is_string($this->outputExpectedRegex), $this, TestCase::class)();
|
$outputBuffer = Closure::bind(fn () => $this->outputBuffer, $this, TestCase::class)();
|
||||||
|
|
||||||
if ($hasOutputExpectation) {
|
if ($outputBuffer->hasExpectation()) {
|
||||||
ob_clean();
|
ob_clean();
|
||||||
|
|
||||||
Closure::bind(function (): void {
|
Closure::bind(function (): void {
|
||||||
$this->outputExpectedString = null;
|
$this->expectedString = null;
|
||||||
$this->outputExpectedRegex = null;
|
$this->expectedRegularExpression = null;
|
||||||
}, $this, TestCase::class)();
|
}, $outputBuffer, OutputBuffer::class)();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->setUp();
|
$this->setUp();
|
||||||
@@ -455,7 +457,9 @@ trait Testable
|
|||||||
*/
|
*/
|
||||||
private function __isExpectedException(Throwable $e): bool
|
private function __isExpectedException(Throwable $e): bool
|
||||||
{
|
{
|
||||||
$read = fn (string $property): mixed => Closure::bind(fn () => $this->{$property}, $this, TestCase::class)();
|
$expectation = Closure::bind(fn () => $this->exceptionExpectation, $this, TestCase::class)();
|
||||||
|
|
||||||
|
$read = fn (string $property): mixed => Closure::bind(fn () => $this->{$property}, $expectation, ExceptionExpectation::class)();
|
||||||
|
|
||||||
$expectedClass = $read('expectedException');
|
$expectedClass = $read('expectedException');
|
||||||
|
|
||||||
@@ -463,13 +467,19 @@ trait Testable
|
|||||||
return $e instanceof $expectedClass;
|
return $e instanceof $expectedClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
$expectedMessage = $read('expectedExceptionMessage');
|
$expectedMessage = $read('expectedMessage');
|
||||||
|
|
||||||
if ($expectedMessage !== null) {
|
if ($expectedMessage !== null) {
|
||||||
return str_contains($e->getMessage(), (string) $expectedMessage);
|
return str_contains($e->getMessage(), (string) $expectedMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
$expectedCode = $read('expectedExceptionCode');
|
$expectedMessageRegex = $read('expectedMessageRegularExpression');
|
||||||
|
|
||||||
|
if ($expectedMessageRegex !== null) {
|
||||||
|
return preg_match($expectedMessageRegex, $e->getMessage()) === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$expectedCode = $read('expectedCode');
|
||||||
|
|
||||||
if ($expectedCode !== null) {
|
if ($expectedCode !== null) {
|
||||||
return $e->getCode() === $expectedCode;
|
return $e->getCode() === $expectedCode;
|
||||||
|
|||||||
@@ -119,6 +119,14 @@ final readonly class Configuration
|
|||||||
return new Browser\Configuration;
|
return new Browser\Configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the evals configuration.
|
||||||
|
*/
|
||||||
|
public function evals(): Evals\Configuration // @phpstan-ignore-line
|
||||||
|
{
|
||||||
|
return new Evals\Configuration; // @phpstan-ignore-line
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the TIA (Test Impact Analysis) configuration.
|
* Gets the TIA (Test Impact Analysis) configuration.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Exceptions;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class DatasetProviderError extends RuntimeException
|
||||||
|
{
|
||||||
|
public function __construct(Throwable $previous)
|
||||||
|
{
|
||||||
|
parent::__construct($previous->getMessage(), (int) $previous->getCode(), $previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Exceptions;
|
||||||
|
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||||
|
use Pest\Contracts\Panicable;
|
||||||
|
use RuntimeException;
|
||||||
|
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class TiaRequiresRepositoryRoot extends RuntimeException implements ExceptionInterface, Panicable, RenderlessEditor, RenderlessTrace
|
||||||
|
{
|
||||||
|
public function __construct(private readonly string $subdirectoryPrefix)
|
||||||
|
{
|
||||||
|
parent::__construct(sprintf(
|
||||||
|
'Tia mode requires the project root to be the git repository root, but it sits in the subdirectory [%s] of a larger repo.',
|
||||||
|
$this->subdirectoryPrefix,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(OutputInterface $output): void
|
||||||
|
{
|
||||||
|
$output->writeln([
|
||||||
|
'',
|
||||||
|
' <fg=white;options=bold;bg=red> ERROR </> Tia mode requires the git repository root.',
|
||||||
|
'',
|
||||||
|
sprintf(' This project sits in a subdirectory of a larger repo <fg=yellow>%s</>.', $this->subdirectoryPrefix),
|
||||||
|
'',
|
||||||
|
' Give the project its own git repository to use Tia.',
|
||||||
|
'',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exitCode(): int
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -130,7 +130,7 @@ final class Expectation
|
|||||||
if (getenv('PARATEST') !== false || isset($_SERVER['COLLISION_PRINTER'])) {
|
if (getenv('PARATEST') !== false || isset($_SERVER['COLLISION_PRINTER'])) {
|
||||||
ob_start();
|
ob_start();
|
||||||
var_dump($this->value, ...$arguments);
|
var_dump($this->value, ...$arguments);
|
||||||
$output = (string) ob_get_clean();
|
$output = ob_get_clean();
|
||||||
|
|
||||||
throw new ExpectationFailedException($output);
|
throw new ExpectationFailedException($output);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ final class HigherOrderExpectation
|
|||||||
* Dynamically calls methods on the class with the given arguments.
|
* Dynamically calls methods on the class with the given arguments.
|
||||||
*
|
*
|
||||||
* @param array<int, mixed> $arguments
|
* @param array<int, mixed> $arguments
|
||||||
* @return self<TOriginalValue, mixed>|self<TOriginalValue, TValue>
|
* @return self<TOriginalValue, mixed>
|
||||||
*/
|
*/
|
||||||
public function __call(string $name, array $arguments): self
|
public function __call(string $name, array $arguments): self
|
||||||
{
|
{
|
||||||
@@ -127,7 +127,7 @@ final class HigherOrderExpectation
|
|||||||
/**
|
/**
|
||||||
* Accesses properties in the value or in the expectation.
|
* Accesses properties in the value or in the expectation.
|
||||||
*
|
*
|
||||||
* @return self<TOriginalValue, mixed>|self<TOriginalValue, TValue>
|
* @return self<TOriginalValue, mixed>
|
||||||
*/
|
*/
|
||||||
public function __get(string $name): self
|
public function __get(string $name): self
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -576,7 +576,7 @@ final readonly class OppositeExpectation
|
|||||||
|
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
$original,
|
$original,
|
||||||
fn (ObjectDescription $object): bool => array_all($traits, fn (string $trait): bool => ! (isset($object->reflectionClass) && in_array($trait, $object->reflectionClass->getTraitNames(), true))),
|
fn (ObjectDescription $object): bool => array_all($traits, fn (string $trait): bool => ! isset($object->reflectionClass) || ! in_array($trait, $object->reflectionClass->getTraitNames(), true)),
|
||||||
"not to use traits '".implode("', '", $traits)."'",
|
"not to use traits '".implode("', '", $traits)."'",
|
||||||
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
||||||
);
|
);
|
||||||
@@ -596,7 +596,7 @@ final readonly class OppositeExpectation
|
|||||||
|
|
||||||
return Targeted::make(
|
return Targeted::make(
|
||||||
$original,
|
$original,
|
||||||
fn (ObjectDescription $object): bool => array_all($interfaces, fn (string $interface): bool => ! (isset($object->reflectionClass) && $object->reflectionClass->implementsInterface($interface))),
|
fn (ObjectDescription $object): bool => array_all($interfaces, fn (string $interface): bool => ! isset($object->reflectionClass) || ! $object->reflectionClass->implementsInterface($interface)),
|
||||||
"not to implement '".implode("', '", $interfaces)."'",
|
"not to implement '".implode("', '", $interfaces)."'",
|
||||||
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
FileLineFinder::where(fn (string $line): bool => str_contains($line, 'class')),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -94,7 +94,8 @@ final class TestCaseFactory
|
|||||||
$filename = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', static fn (array $match): string => strtolower($match['drive']), $filename);
|
$filename = (string) preg_replace_callback('~^(?P<drive>[a-z]+:\\\)~i', static fn (array $match): string => strtolower($match['drive']), $filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
$filename = str_replace('\\\\', '\\', addslashes((string) realpath($filename)));
|
$realpath = (string) realpath($filename);
|
||||||
|
$filename = str_replace('\\\\', '\\', addslashes($realpath));
|
||||||
$rootPath = TestSuite::getInstance()->rootPath;
|
$rootPath = TestSuite::getInstance()->rootPath;
|
||||||
$relativePath = str_replace($rootPath.DIRECTORY_SEPARATOR, '', $filename);
|
$relativePath = str_replace($rootPath.DIRECTORY_SEPARATOR, '', $filename);
|
||||||
|
|
||||||
@@ -149,6 +150,8 @@ final class TestCaseFactory
|
|||||||
|
|
||||||
$attributesCode = Attributes::code($this->attributes);
|
$attributesCode = Attributes::code($this->attributes);
|
||||||
|
|
||||||
|
$filenameLiteral = var_export($realpath, true);
|
||||||
|
|
||||||
$methodsCode = implode('', array_map(
|
$methodsCode = implode('', array_map(
|
||||||
fn (TestCaseMethodFactory $methodFactory): string => $methodFactory->buildForEvaluation(),
|
fn (TestCaseMethodFactory $methodFactory): string => $methodFactory->buildForEvaluation(),
|
||||||
$methods
|
$methods
|
||||||
@@ -158,6 +161,7 @@ final class TestCaseFactory
|
|||||||
$classCode = <<<PHP
|
$classCode = <<<PHP
|
||||||
namespace $namespace;
|
namespace $namespace;
|
||||||
|
|
||||||
|
use Pest\Exceptions\DatasetProviderError as __PestDatasetProviderError;
|
||||||
use Pest\Repositories\DatasetsRepository as __PestDatasets;
|
use Pest\Repositories\DatasetsRepository as __PestDatasets;
|
||||||
use Pest\TestSuite as __PestTestSuite;
|
use Pest\TestSuite as __PestTestSuite;
|
||||||
|
|
||||||
@@ -166,7 +170,7 @@ final class TestCaseFactory
|
|||||||
final class $className extends $baseClass implements $hasPrintableTestCaseClassFQN {
|
final class $className extends $baseClass implements $hasPrintableTestCaseClassFQN {
|
||||||
$traitsCode
|
$traitsCode
|
||||||
|
|
||||||
public static \$__filename = '$filename';
|
public static \$__filename = $filenameLiteral;
|
||||||
|
|
||||||
$methodsCode
|
$methodsCode
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,6 +235,10 @@ final class TestCaseMethodFactory
|
|||||||
$attributesCode
|
$attributesCode
|
||||||
public function $methodName(...\$arguments)
|
public function $methodName(...\$arguments)
|
||||||
{
|
{
|
||||||
|
if (count(\$arguments) === 1 && \$arguments[0] instanceof __PestDatasetProviderError) {
|
||||||
|
throw \$arguments[0]->getPrevious() ?? \$arguments[0];
|
||||||
|
}
|
||||||
|
|
||||||
return \$this->__runTest(
|
return \$this->__runTest(
|
||||||
\$this->__test,
|
\$this->__test,
|
||||||
...\$arguments,
|
...\$arguments,
|
||||||
@@ -261,7 +265,11 @@ final class TestCaseMethodFactory
|
|||||||
|
|
||||||
public static function $dataProviderName()
|
public static function $dataProviderName()
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
return __PestDatasets::get(self::\$__filename, "$methodName");
|
return __PestDatasets::get(self::\$__filename, "$methodName");
|
||||||
|
} catch (\Throwable \$throwable) {
|
||||||
|
return [[new __PestDatasetProviderError(\$throwable)]];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EOF;
|
EOF;
|
||||||
|
|||||||
+20
-21
@@ -15,11 +15,6 @@ use PHPUnit\Event\Test\AfterLastTestMethodErrored;
|
|||||||
use PHPUnit\Event\Test\AfterLastTestMethodFailed;
|
use PHPUnit\Event\Test\AfterLastTestMethodFailed;
|
||||||
use PHPUnit\Event\Test\BeforeFirstTestMethodErrored;
|
use PHPUnit\Event\Test\BeforeFirstTestMethodErrored;
|
||||||
use PHPUnit\Event\Test\BeforeFirstTestMethodFailed;
|
use PHPUnit\Event\Test\BeforeFirstTestMethodFailed;
|
||||||
use PHPUnit\Event\Test\ConsideredRisky;
|
|
||||||
use PHPUnit\Event\Test\Errored;
|
|
||||||
use PHPUnit\Event\Test\Failed;
|
|
||||||
use PHPUnit\Event\Test\MarkedIncomplete;
|
|
||||||
use PHPUnit\Event\Test\Skipped;
|
|
||||||
use PHPUnit\Event\TestSuite\TestSuite;
|
use PHPUnit\Event\TestSuite\TestSuite;
|
||||||
use PHPUnit\Event\TestSuite\TestSuiteForTestMethodWithDataProvider;
|
use PHPUnit\Event\TestSuite\TestSuiteForTestMethodWithDataProvider;
|
||||||
use PHPUnit\Framework\Exception as FrameworkException;
|
use PHPUnit\Framework\Exception as FrameworkException;
|
||||||
@@ -254,25 +249,29 @@ final readonly class Converter
|
|||||||
...$result->testMarkedIncompleteEvents(),
|
...$result->testMarkedIncompleteEvents(),
|
||||||
];
|
];
|
||||||
|
|
||||||
$numberOfNotPassedTests = count(
|
$notPassedTests = [];
|
||||||
array_unique(
|
|
||||||
array_map(
|
foreach ($events as $event) {
|
||||||
function (AfterLastTestMethodErrored|AfterLastTestMethodFailed|BeforeFirstTestMethodErrored|BeforeFirstTestMethodFailed|Errored|Failed|Skipped|ConsideredRisky|MarkedIncomplete $event): string {
|
if ($event instanceof AfterLastTestMethodErrored) {
|
||||||
if ($event instanceof BeforeFirstTestMethodErrored
|
// PHPUnit's collector does not count these towards `numberOfTestsRun`...
|
||||||
|| $event instanceof AfterLastTestMethodErrored
|
continue;
|
||||||
|| $event instanceof BeforeFirstTestMethodFailed
|
}
|
||||||
|| $event instanceof AfterLastTestMethodFailed) {
|
if ($event instanceof AfterLastTestMethodFailed) {
|
||||||
return $event->testClassName();
|
// PHPUnit's collector does not count these towards `numberOfTestsRun`...
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($event instanceof BeforeFirstTestMethodErrored || $event instanceof BeforeFirstTestMethodFailed) {
|
||||||
|
$notPassedTests[] = $event->testClassName();
|
||||||
|
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->getTestCaseLocation($event->test());
|
$notPassedTests[] = $this->getTestCaseLocation($event->test());
|
||||||
},
|
}
|
||||||
$events
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
$numberOfPassedTests = $result->numberOfTestsRun() - $numberOfNotPassedTests;
|
$numberOfPassedTests = $result->numberOfTestsRun()
|
||||||
|
- count(array_unique($notPassedTests))
|
||||||
|
- $result->numberOfTestSkippedByTestSuiteSkippedEvents();
|
||||||
|
|
||||||
return $this->stateGenerator->fromPhpUnitTestResult($numberOfPassedTests, $result);
|
return $this->stateGenerator->fromPhpUnitTestResult($numberOfPassedTests, $result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ final class TestSkippedSubscriber extends Subscriber implements SkippedSubscribe
|
|||||||
{
|
{
|
||||||
public function notify(Skipped $event): void
|
public function notify(Skipped $event): void
|
||||||
{
|
{
|
||||||
|
if ($event->message() === '__TODO__') {
|
||||||
|
return; // "todo" tests are reported in the summary, not as ignored tests...
|
||||||
|
}
|
||||||
|
|
||||||
$this->logger()->testSkipped($event);
|
$this->logger()->testSkipped($event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,6 +241,12 @@ final class TeamCityLogger
|
|||||||
$telemetry->memoryUsageSinceStart(),
|
$telemetry->memoryUsageSinceStart(),
|
||||||
$telemetry->durationSincePrevious(),
|
$telemetry->durationSincePrevious(),
|
||||||
$telemetry->memoryUsageSincePrevious(),
|
$telemetry->memoryUsageSincePrevious(),
|
||||||
|
$telemetry->userCpuTimeSinceStart(),
|
||||||
|
$telemetry->systemCpuTimeSinceStart(),
|
||||||
|
$telemetry->totalCpuTimeSinceStart(),
|
||||||
|
$telemetry->userCpuTimeSincePrevious(),
|
||||||
|
$telemetry->systemCpuTimeSincePrevious(),
|
||||||
|
$telemetry->totalCpuTimeSincePrevious(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1219,4 +1219,104 @@ final class Expectation
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is an IP address.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeIpAddress(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert::assertTrue((bool) filter_var($this->value, FILTER_VALIDATE_IP), $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is a MAC address.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeMacAddress(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert::assertTrue((bool) filter_var($this->value, FILTER_VALIDATE_MAC), $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is a hostname.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeHostname(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert::assertTrue((bool) filter_var($this->value, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME), $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is a domain name.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeDomain(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
$isValid = filter_var($this->value, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) !== false
|
||||||
|
&& str_contains($this->value, '.');
|
||||||
|
|
||||||
|
Assert::assertTrue($isValid, $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is a base64-encoded string.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeBase64(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
$decoded = base64_decode($this->value, true);
|
||||||
|
Assert::assertTrue($decoded !== false && base64_encode($decoded) === $this->value, $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the value is a hexadecimal string.
|
||||||
|
*
|
||||||
|
* @return self<TValue>
|
||||||
|
*/
|
||||||
|
public function toBeHexadecimal(string $message = ''): self
|
||||||
|
{
|
||||||
|
if (! is_string($this->value)) {
|
||||||
|
InvalidExpectationValue::expected('string');
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert::assertTrue(ctype_xdigit($this->value), $message);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use PHPStan\Type\Type;
|
|||||||
* $expectation, $opposite, $shouldReset) from being incorrectly resolved as
|
* $expectation, $opposite, $shouldReset) from being incorrectly resolved as
|
||||||
* higher-order value property accesses by downstream ExpressionTypeResolverExtensions.
|
* higher-order value property accesses by downstream ExpressionTypeResolverExtensions.
|
||||||
*
|
*
|
||||||
* This extension must be registered BEFORE the peststan HigherOrderExpectationTypeExtension.
|
* This extension must be registered BEFORE the pest-plugin-phpstan HigherOrderExpectationTypeExtension.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ namespace Pest;
|
|||||||
|
|
||||||
function version(): string
|
function version(): string
|
||||||
{
|
{
|
||||||
return '5.0.0-rc.11';
|
return '5.0.0-beta.1';
|
||||||
}
|
}
|
||||||
|
|
||||||
function testDirectory(string $file = ''): string
|
function testDirectory(string $file = ''): string
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ final class Cache implements HandlesArguments
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->hasArgument('--parallel', $arguments)) {
|
if (! $this->hasArgument('--parallel', $arguments) && ! $this->hasArgument('--do-not-cache-result', $arguments) && ! $this->hasArgument('--cache-result', $arguments)) {
|
||||||
return $this->pushArgument('--cache-result', $arguments);
|
return $this->pushArgument('--cache-result', $arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,10 +120,6 @@ final class ResultPrinter
|
|||||||
|
|
||||||
$unexpectedOutput = $this->tail($outputFile);
|
$unexpectedOutput = $this->tail($outputFile);
|
||||||
if ($unexpectedOutput !== '') {
|
if ($unexpectedOutput !== '') {
|
||||||
if (preg_match('/^T+$/', $unexpectedOutput) > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->output->write($unexpectedOutput);
|
$this->output->write($unexpectedOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use ParaTest\JUnit\LogMerger;
|
|||||||
use ParaTest\JUnit\Writer;
|
use ParaTest\JUnit\Writer;
|
||||||
use ParaTest\Options;
|
use ParaTest\Options;
|
||||||
use ParaTest\RunnerInterface;
|
use ParaTest\RunnerInterface;
|
||||||
|
use ParaTest\WrapperRunner\MissingResultsException;
|
||||||
use ParaTest\WrapperRunner\SuiteLoader;
|
use ParaTest\WrapperRunner\SuiteLoader;
|
||||||
use ParaTest\WrapperRunner\WrapperWorker;
|
use ParaTest\WrapperRunner\WrapperWorker;
|
||||||
use Pest\Result;
|
use Pest\Result;
|
||||||
@@ -42,6 +43,8 @@ use function assert;
|
|||||||
use function count;
|
use function count;
|
||||||
use function dirname;
|
use function dirname;
|
||||||
use function file_get_contents;
|
use function file_get_contents;
|
||||||
|
use function filesize;
|
||||||
|
use function is_file;
|
||||||
use function max;
|
use function max;
|
||||||
use function realpath;
|
use function realpath;
|
||||||
use function str_starts_with;
|
use function str_starts_with;
|
||||||
@@ -54,14 +57,14 @@ use function usleep;
|
|||||||
*/
|
*/
|
||||||
final class WrapperRunner implements RunnerInterface
|
final class WrapperRunner implements RunnerInterface
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The time to sleep between cycles.
|
|
||||||
*/
|
|
||||||
/**
|
/**
|
||||||
* The merged test result from the parallel run.
|
* The merged test result from the parallel run.
|
||||||
*/
|
*/
|
||||||
public static ?TestResult $result = null;
|
public static ?TestResult $result = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time to sleep between cycles.
|
||||||
|
*/
|
||||||
private const int CYCLE_SLEEP = 10000;
|
private const int CYCLE_SLEEP = 10000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,6 +91,18 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
/** @var array<int,int> */
|
/** @var array<int,int> */
|
||||||
private array $batches = [];
|
private array $batches = [];
|
||||||
|
|
||||||
|
/** @var array<non-empty-string,true> */
|
||||||
|
private array $requiredTestResultFiles = [];
|
||||||
|
|
||||||
|
/** @var array<non-empty-string,true> */
|
||||||
|
private array $requiredCoverageFiles = [];
|
||||||
|
|
||||||
|
/** @var list<SplFileInfo> */
|
||||||
|
private array $statusFiles = [];
|
||||||
|
|
||||||
|
/** @var list<SplFileInfo> */
|
||||||
|
private array $progressFiles = [];
|
||||||
|
|
||||||
/** @var list<SplFileInfo> */
|
/** @var list<SplFileInfo> */
|
||||||
private array $unexpectedOutputFiles = [];
|
private array $unexpectedOutputFiles = [];
|
||||||
|
|
||||||
@@ -217,7 +232,7 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
$this->exitcode > 0
|
$this->exitcode > 0
|
||||||
&& $this->options->configuration->stopOnFailure()
|
&& $this->options->configuration->stopOnFailureThreshold() > 0
|
||||||
) {
|
) {
|
||||||
$this->pending = [];
|
$this->pending = [];
|
||||||
} elseif (($pending = array_shift($this->pending)) !== null) {
|
} elseif (($pending = array_shift($this->pending)) !== null) {
|
||||||
@@ -232,6 +247,18 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
|
|
||||||
private function flushWorker(WrapperWorker $worker): void
|
private function flushWorker(WrapperWorker $worker): void
|
||||||
{
|
{
|
||||||
|
if ($worker->hasExecutedTests()) {
|
||||||
|
$testResultFile = $worker->testResultFile->getPathname();
|
||||||
|
|
||||||
|
if ($testResultFile !== '') {
|
||||||
|
$this->requiredTestResultFiles[$testResultFile] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($worker->coverageFile) && $worker->coverageFile->getPathname() !== '') {
|
||||||
|
$this->requiredCoverageFiles[$worker->coverageFile->getPathname()] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->exitcode = max($this->exitcode, $worker->getExitCode());
|
$this->exitcode = max($this->exitcode, $worker->getExitCode());
|
||||||
$this->printer->printFeedback(
|
$this->printer->printFeedback(
|
||||||
$worker->progressFile,
|
$worker->progressFile,
|
||||||
@@ -279,10 +306,15 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
$worker->start();
|
$worker->start();
|
||||||
$this->batches[$token] = 0;
|
$this->batches[$token] = 0;
|
||||||
|
|
||||||
$this->unexpectedOutputFiles[] = $worker->unexpectedOutputFile;
|
$this->statusFiles[] = $worker->statusFile;
|
||||||
|
$this->progressFiles[] = $worker->progressFile;
|
||||||
$this->unexpectedOutputFiles[] = $worker->unexpectedOutputFile;
|
$this->unexpectedOutputFiles[] = $worker->unexpectedOutputFile;
|
||||||
$this->testResultFiles[] = $worker->testResultFile;
|
$this->testResultFiles[] = $worker->testResultFile;
|
||||||
|
|
||||||
|
if (isset($worker->resultCacheFile)) {
|
||||||
|
$this->resultCacheFiles[] = $worker->resultCacheFile;
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($worker->junitFile)) {
|
if (isset($worker->junitFile)) {
|
||||||
$this->junitFiles[] = $worker->junitFile;
|
$this->junitFiles[] = $worker->junitFile;
|
||||||
}
|
}
|
||||||
@@ -315,6 +347,20 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
|
|
||||||
private function complete(TestResult $testResultSum): int
|
private function complete(TestResult $testResultSum): int
|
||||||
{
|
{
|
||||||
|
$missingTestResultFiles = [];
|
||||||
|
|
||||||
|
foreach ($this->requiredTestResultFiles as $filePath => $true) {
|
||||||
|
if (is_file($filePath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$missingTestResultFiles[] = $filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($missingTestResultFiles !== []) {
|
||||||
|
throw MissingResultsException::create($missingTestResultFiles, 'test_result');
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($this->testResultFiles as $testResultFile) {
|
foreach ($this->testResultFiles as $testResultFile) {
|
||||||
if (! $testResultFile->isFile()) {
|
if (! $testResultFile->isFile()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -349,6 +395,20 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
// @phpstan-ignore-next-line
|
// @phpstan-ignore-next-line
|
||||||
array_merge_recursive($testResultSum->testRunnerTriggeredWarningEvents(), $testResult->testRunnerTriggeredWarningEvents()),
|
array_merge_recursive($testResultSum->testRunnerTriggeredWarningEvents(), $testResult->testRunnerTriggeredWarningEvents()),
|
||||||
// @phpstan-ignore-next-line
|
// @phpstan-ignore-next-line
|
||||||
|
array_merge_recursive($testResultSum->testRunnerTriggeredIssueDeprecationEvents(), $testResult->testRunnerTriggeredIssueDeprecationEvents()),
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
array_merge_recursive($testResultSum->testRunnerTriggeredIssueErrorEvents(), $testResult->testRunnerTriggeredIssueErrorEvents()),
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
array_merge_recursive($testResultSum->testRunnerTriggeredIssueNoticeEvents(), $testResult->testRunnerTriggeredIssueNoticeEvents()),
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
array_merge_recursive($testResultSum->testRunnerTriggeredIssuePhpDeprecationEvents(), $testResult->testRunnerTriggeredIssuePhpDeprecationEvents()),
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
array_merge_recursive($testResultSum->testRunnerTriggeredIssuePhpNoticeEvents(), $testResult->testRunnerTriggeredIssuePhpNoticeEvents()),
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
array_merge_recursive($testResultSum->testRunnerTriggeredIssuePhpWarningEvents(), $testResult->testRunnerTriggeredIssuePhpWarningEvents()),
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
array_merge_recursive($testResultSum->testRunnerTriggeredIssueWarningEvents(), $testResult->testRunnerTriggeredIssueWarningEvents()),
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
array_merge_recursive($testResultSum->errors(), $testResult->errors()),
|
array_merge_recursive($testResultSum->errors(), $testResult->errors()),
|
||||||
// @phpstan-ignore-next-line
|
// @phpstan-ignore-next-line
|
||||||
array_merge_recursive($testResultSum->deprecations(), $testResult->deprecations()),
|
array_merge_recursive($testResultSum->deprecations(), $testResult->deprecations()),
|
||||||
@@ -384,8 +444,15 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
$testResultSum->testRunnerTriggeredNoticeEvents(),
|
$testResultSum->testRunnerTriggeredNoticeEvents(),
|
||||||
array_values(array_filter(
|
array_values(array_filter(
|
||||||
$testResultSum->testRunnerTriggeredWarningEvents(),
|
$testResultSum->testRunnerTriggeredWarningEvents(),
|
||||||
fn (WarningTriggered $event): bool => ! str_contains($event->message(), 'No tests found')
|
fn (WarningTriggered $event): bool => ! str_contains($event->message(), 'No tests found in class')
|
||||||
)),
|
)),
|
||||||
|
$testResultSum->testRunnerTriggeredIssueDeprecationEvents(),
|
||||||
|
$testResultSum->testRunnerTriggeredIssueErrorEvents(),
|
||||||
|
$testResultSum->testRunnerTriggeredIssueNoticeEvents(),
|
||||||
|
$testResultSum->testRunnerTriggeredIssuePhpDeprecationEvents(),
|
||||||
|
$testResultSum->testRunnerTriggeredIssuePhpNoticeEvents(),
|
||||||
|
$testResultSum->testRunnerTriggeredIssuePhpWarningEvents(),
|
||||||
|
$testResultSum->testRunnerTriggeredIssueWarningEvents(),
|
||||||
$testResultSum->errors(),
|
$testResultSum->errors(),
|
||||||
$testResultSum->deprecations(),
|
$testResultSum->deprecations(),
|
||||||
$testResultSum->notices(),
|
$testResultSum->notices(),
|
||||||
@@ -421,8 +488,11 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
|
|
||||||
$exitcode = Result::exitCode($this->options->configuration, $testResultSum);
|
$exitcode = Result::exitCode($this->options->configuration, $testResultSum);
|
||||||
|
|
||||||
|
$this->clearFiles($this->statusFiles);
|
||||||
|
$this->clearFiles($this->progressFiles);
|
||||||
$this->clearFiles($this->unexpectedOutputFiles);
|
$this->clearFiles($this->unexpectedOutputFiles);
|
||||||
$this->clearFiles($this->testResultFiles);
|
$this->clearFiles($this->testResultFiles);
|
||||||
|
$this->clearFiles($this->resultCacheFiles);
|
||||||
$this->clearFiles($this->coverageFiles);
|
$this->clearFiles($this->coverageFiles);
|
||||||
$this->clearFiles($this->junitFiles);
|
$this->clearFiles($this->junitFiles);
|
||||||
$this->clearFiles($this->teamcityFiles);
|
$this->clearFiles($this->teamcityFiles);
|
||||||
@@ -437,6 +507,20 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$missingCoverageFiles = [];
|
||||||
|
|
||||||
|
foreach ($this->requiredCoverageFiles as $filePath => $true) {
|
||||||
|
if (is_file($filePath) && filesize($filePath) !== 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$missingCoverageFiles[] = $filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($missingCoverageFiles !== []) {
|
||||||
|
throw MissingResultsException::create($missingCoverageFiles, 'coverage');
|
||||||
|
}
|
||||||
|
|
||||||
$coverageManager = new CodeCoverage;
|
$coverageManager = new CodeCoverage;
|
||||||
$coverageManager->init(
|
$coverageManager->init(
|
||||||
$this->options->configuration,
|
$this->options->configuration,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace Pest\Plugins\Parallel\Support;
|
|||||||
use NunoMaduro\Collision\Adapters\Phpunit\State;
|
use NunoMaduro\Collision\Adapters\Phpunit\State;
|
||||||
use NunoMaduro\Collision\Adapters\Phpunit\Style;
|
use NunoMaduro\Collision\Adapters\Phpunit\Style;
|
||||||
use ParaTest\Options;
|
use ParaTest\Options;
|
||||||
|
use PHPUnit\Event\Telemetry\CpuTime;
|
||||||
use PHPUnit\Event\Telemetry\GarbageCollectorStatus;
|
use PHPUnit\Event\Telemetry\GarbageCollectorStatus;
|
||||||
use PHPUnit\Event\Telemetry\HRTime;
|
use PHPUnit\Event\Telemetry\HRTime;
|
||||||
use PHPUnit\Event\Telemetry\Info;
|
use PHPUnit\Event\Telemetry\Info;
|
||||||
@@ -147,11 +148,20 @@ final class CompactPrinter
|
|||||||
MemoryUsage::fromBytes(0),
|
MemoryUsage::fromBytes(0),
|
||||||
MemoryUsage::fromBytes(0),
|
MemoryUsage::fromBytes(0),
|
||||||
$garbageCollectorStatus,
|
$garbageCollectorStatus,
|
||||||
|
CpuTime::fromSecondsAndNanoseconds(0, 0),
|
||||||
|
CpuTime::fromSecondsAndNanoseconds(0, 0),
|
||||||
|
CpuTime::fromSecondsAndNanoseconds(0, 0),
|
||||||
),
|
),
|
||||||
$telemetryDuration,
|
$telemetryDuration,
|
||||||
MemoryUsage::fromBytes(0),
|
MemoryUsage::fromBytes(0),
|
||||||
\PHPUnit\Event\Telemetry\Duration::fromSecondsAndNanoseconds(0, 0),
|
\PHPUnit\Event\Telemetry\Duration::fromSecondsAndNanoseconds(0, 0),
|
||||||
MemoryUsage::fromBytes(0),
|
MemoryUsage::fromBytes(0),
|
||||||
|
CpuTime::fromSecondsAndNanoseconds(0, 0),
|
||||||
|
CpuTime::fromSecondsAndNanoseconds(0, 0),
|
||||||
|
CpuTime::fromSecondsAndNanoseconds(0, 0),
|
||||||
|
CpuTime::fromSecondsAndNanoseconds(0, 0),
|
||||||
|
CpuTime::fromSecondsAndNanoseconds(0, 0),
|
||||||
|
CpuTime::fromSecondsAndNanoseconds(0, 0),
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->style->writeRecap($state, $telemetry, $testResult);
|
$this->style->writeRecap($state, $telemetry, $testResult);
|
||||||
|
|||||||
+78
-5
@@ -9,6 +9,7 @@ use Pest\Contracts\Plugins\AddsOutput;
|
|||||||
use Pest\Contracts\Plugins\HandlesArguments;
|
use Pest\Contracts\Plugins\HandlesArguments;
|
||||||
use Pest\Contracts\Plugins\Terminable;
|
use Pest\Contracts\Plugins\Terminable;
|
||||||
use Pest\Exceptions\NoAffectedTestsFound;
|
use Pest\Exceptions\NoAffectedTestsFound;
|
||||||
|
use Pest\Exceptions\TiaRequiresRepositoryRoot;
|
||||||
use Pest\Panic;
|
use Pest\Panic;
|
||||||
use Pest\Plugins\Concerns\HandleArguments;
|
use Pest\Plugins\Concerns\HandleArguments;
|
||||||
use Pest\Plugins\Tia\BaselineSync;
|
use Pest\Plugins\Tia\BaselineSync;
|
||||||
@@ -219,8 +220,11 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
if (! $watchPatterns->isEnabled()) {
|
if (! $watchPatterns->isEnabled()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (! $watchPatterns->isLocally()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return ! ($watchPatterns->isLocally() && self::argumentPresent('--ci', $arguments));
|
return ! self::argumentPresent('--ci', $arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -292,7 +296,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
$result = $this->replayGraph->getResult($this->branch, $testId);
|
$result = $this->replayGraph->getResult($this->branch, $testId);
|
||||||
|
|
||||||
if ($result instanceof TestStatus) {
|
if ($result instanceof TestStatus) {
|
||||||
if ($result->isFailure() || $result->isError()) {
|
if ($this->replayGraph->shouldRerunStatus($result)) {
|
||||||
$this->executedCount++;
|
$this->executedCount++;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -398,7 +402,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
|
|
||||||
$projectRoot = TestSuite::getInstance()->rootPath;
|
$projectRoot = TestSuite::getInstance()->rootPath;
|
||||||
$perTest = $this->piggybackCoverage
|
$perTest = $this->piggybackCoverage
|
||||||
? $this->coverageCollector->perTestFiles()
|
? $this->mergePerTestFiles($this->coverageCollector->perTestFiles(), $recorder->perTestFiles())
|
||||||
: $recorder->perTestFiles();
|
: $recorder->perTestFiles();
|
||||||
|
|
||||||
if ($perTest === []) {
|
if ($perTest === []) {
|
||||||
@@ -558,7 +562,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
return $exitCode;
|
return $exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->snapshotTestResults();
|
$this->snapshotTestResults(markKnownTestFiles: true);
|
||||||
|
|
||||||
return $exitCode;
|
return $exitCode;
|
||||||
}
|
}
|
||||||
@@ -624,6 +628,13 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
private function handleParent(array $arguments, string $projectRoot, bool $forceRebuild): array
|
private function handleParent(array $arguments, string $projectRoot, bool $forceRebuild): array
|
||||||
{
|
{
|
||||||
$this->watchPatterns->useDefaults($projectRoot);
|
$this->watchPatterns->useDefaults($projectRoot);
|
||||||
|
|
||||||
|
$subdirectoryPrefix = $this->gitSubdirectoryPrefix($projectRoot);
|
||||||
|
|
||||||
|
if ($subdirectoryPrefix !== null) {
|
||||||
|
Panic::with(new TiaRequiresRepositoryRoot($subdirectoryPrefix));
|
||||||
|
}
|
||||||
|
|
||||||
$this->branch = new ChangedFiles($projectRoot)->currentBranch() ?? 'main';
|
$this->branch = new ChangedFiles($projectRoot)->currentBranch() ?? 'main';
|
||||||
|
|
||||||
$fingerprint = Fingerprint::compute($projectRoot);
|
$fingerprint = Fingerprint::compute($projectRoot);
|
||||||
@@ -704,6 +715,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($this->piggybackCoverage) {
|
if ($this->piggybackCoverage) {
|
||||||
|
$this->recorder->activateLinkTracking();
|
||||||
$this->recordingActive = true;
|
$this->recordingActive = true;
|
||||||
|
|
||||||
return $arguments;
|
return $arguments;
|
||||||
@@ -771,6 +783,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
private function activateWorkerRecorderForReplay(array $arguments): array
|
private function activateWorkerRecorderForReplay(array $arguments): array
|
||||||
{
|
{
|
||||||
if ($this->piggybackCoverage) {
|
if ($this->piggybackCoverage) {
|
||||||
|
$this->recorder->activateLinkTracking();
|
||||||
$this->recordingActive = true;
|
$this->recordingActive = true;
|
||||||
|
|
||||||
return $arguments;
|
return $arguments;
|
||||||
@@ -823,6 +836,13 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
$affectedFromChanges = $changed === [] ? [] : $graph->affected($changed);
|
$affectedFromChanges = $changed === [] ? [] : $graph->affected($changed);
|
||||||
$rerunFromCache = [];
|
$rerunFromCache = [];
|
||||||
|
|
||||||
|
if ($this->filteredMode && $graph->hasUnlocatedTestsToRerun($this->branch)) {
|
||||||
|
$this->filteredMode = false;
|
||||||
|
|
||||||
|
$this->renderBadge('WARN', 'Some cached tests due a re-run could not be located on disk.');
|
||||||
|
$this->renderChild('Running the full suite with replay instead of a filtered run.');
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->filteredMode) {
|
if ($this->filteredMode) {
|
||||||
$rerunFromCache = $graph->testFilesToRerun($this->branch);
|
$rerunFromCache = $graph->testFilesToRerun($this->branch);
|
||||||
}
|
}
|
||||||
@@ -1003,6 +1023,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($this->piggybackCoverage) {
|
if ($this->piggybackCoverage) {
|
||||||
|
$recorder->activateLinkTracking();
|
||||||
$this->recordingActive = true;
|
$this->recordingActive = true;
|
||||||
|
|
||||||
$this->output->writeln('');
|
$this->output->writeln('');
|
||||||
@@ -1224,6 +1245,8 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
$data = $this->readPartial($key);
|
$data = $this->readPartial($key);
|
||||||
|
|
||||||
if ($data === null) {
|
if ($data === null) {
|
||||||
|
$this->state->delete($key);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1357,6 +1380,26 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
$this->saveGraph($graph);
|
$this->saveGraph($graph);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Union of two per-test edge maps — piggybacked line-coverage edges plus
|
||||||
|
* the recorder's link-tracked edges (rendered Blade views, ...), which
|
||||||
|
* never appear in line coverage.
|
||||||
|
*
|
||||||
|
* @param array<string, array<int, string>> $coverage
|
||||||
|
* @param array<string, array<int, string>> $linked
|
||||||
|
* @return array<string, array<int, string>>
|
||||||
|
*/
|
||||||
|
private function mergePerTestFiles(array $coverage, array $linked): array
|
||||||
|
{
|
||||||
|
foreach ($linked as $testFile => $sources) {
|
||||||
|
$existing = $coverage[$testFile] ?? [];
|
||||||
|
|
||||||
|
$coverage[$testFile] = array_values(array_unique([...$existing, ...$sources]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $coverage;
|
||||||
|
}
|
||||||
|
|
||||||
private function seedResultsInto(Graph $graph): void
|
private function seedResultsInto(Graph $graph): void
|
||||||
{
|
{
|
||||||
/** @var ResultCollector $collector */
|
/** @var ResultCollector $collector */
|
||||||
@@ -1368,6 +1411,10 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
foreach ($results as $testId => $result) {
|
foreach ($results as $testId => $result) {
|
||||||
$file = $result['file'] ?? null;
|
$file = $result['file'] ?? null;
|
||||||
|
|
||||||
|
if ($file === null || str_contains($file, "eval()'d")) {
|
||||||
|
$file = $this->resolveFailedTestFile($testId);
|
||||||
|
}
|
||||||
|
|
||||||
if (is_string($file) && $file !== '') {
|
if (is_string($file) && $file !== '') {
|
||||||
$touchedFiles[$file] = true;
|
$touchedFiles[$file] = true;
|
||||||
}
|
}
|
||||||
@@ -1383,12 +1430,13 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$graph->markKnownTestFiles(array_keys($touchedFiles));
|
||||||
$graph->pruneStaleResults($this->branch, array_keys($touchedFiles), array_keys($results));
|
$graph->pruneStaleResults($this->branch, array_keys($touchedFiles), array_keys($results));
|
||||||
|
|
||||||
$collector->reset();
|
$collector->reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function snapshotTestResults(): void
|
private function snapshotTestResults(bool $markKnownTestFiles = false): void
|
||||||
{
|
{
|
||||||
/** @var ResultCollector $collector */
|
/** @var ResultCollector $collector */
|
||||||
$collector = Container::getInstance()->get(ResultCollector::class);
|
$collector = Container::getInstance()->get(ResultCollector::class);
|
||||||
@@ -1431,6 +1479,10 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($markKnownTestFiles) {
|
||||||
|
$graph->markKnownTestFiles(array_keys($touchedFiles));
|
||||||
|
}
|
||||||
|
|
||||||
$graph->pruneStaleResults($this->branch, array_keys($touchedFiles), array_keys($results));
|
$graph->pruneStaleResults($this->branch, array_keys($touchedFiles), array_keys($results));
|
||||||
|
|
||||||
$this->saveGraph($graph);
|
$this->saveGraph($graph);
|
||||||
@@ -1640,6 +1692,27 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
return implode(', ', array_keys($seen));
|
return implode(', ', array_keys($seen));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path from the git repository root down to $projectRoot (e.g.
|
||||||
|
* `laravel-app`) when the project is nested inside a larger repo, or `null`
|
||||||
|
* when the project root is itself the repo root (or git is unavailable).
|
||||||
|
* TIA requires the two to coincide: git reports and addresses paths
|
||||||
|
* relative to the repo root, while the dependency graph is project-relative.
|
||||||
|
*/
|
||||||
|
private function gitSubdirectoryPrefix(string $projectRoot): ?string
|
||||||
|
{
|
||||||
|
$process = new Process(['git', 'rev-parse', '--show-prefix'], $projectRoot);
|
||||||
|
$process->run();
|
||||||
|
|
||||||
|
if (! $process->isSuccessful()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$prefix = trim($process->getOutput());
|
||||||
|
|
||||||
|
return $prefix === '' ? null : rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $prefix), '/');
|
||||||
|
}
|
||||||
|
|
||||||
private function composerLockDelta(string $projectRoot, string $sha): string
|
private function composerLockDelta(string $projectRoot, string $sha): string
|
||||||
{
|
{
|
||||||
$current = @file_get_contents($projectRoot.'/composer.lock');
|
$current = @file_get_contents($projectRoot.'/composer.lock');
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Plugins\Tia\Contracts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
interface Lockfile
|
||||||
|
{
|
||||||
|
public function applies(string $filename): bool;
|
||||||
|
|
||||||
|
public function fingerprint(string $contents): ?string;
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@ use Pest\Plugins\Tia;
|
|||||||
use Pest\Plugins\Tia\Contracts\State;
|
use Pest\Plugins\Tia\Contracts\State;
|
||||||
use Pest\Support\Container;
|
use Pest\Support\Container;
|
||||||
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
||||||
|
use SebastianBergmann\CodeCoverage\Driver\Selector;
|
||||||
|
use SebastianBergmann\CodeCoverage\Filter;
|
||||||
|
use SebastianBergmann\CodeCoverage\Serialization\Unserializer;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -161,7 +164,60 @@ final class CoverageMerger
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $value instanceof CodeCoverage ? $value : null;
|
// Legacy `--coverage-php` format: a serialized `CodeCoverage` object.
|
||||||
|
if ($value instanceof CodeCoverage) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since phpunit/php-code-coverage 14, `--coverage-php` writes the report
|
||||||
|
// as a serialized array (`['codeCoverage' => ..., 'testResults' => ...,
|
||||||
|
// 'basePath' => ...]`) rather than a `CodeCoverage` object, so it has to
|
||||||
|
// be rebuilt into one before it can be merged.
|
||||||
|
return self::coverageFromSerializedData($reportPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function coverageFromSerializedData(string $reportPath): ?CodeCoverage
|
||||||
|
{
|
||||||
|
if ($reportPath === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$serialized = new Unserializer()->unserialize($reportPath);
|
||||||
|
} catch (Throwable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $serialized['codeCoverage'];
|
||||||
|
$basePath = $serialized['basePath'];
|
||||||
|
|
||||||
|
if ($basePath !== '') {
|
||||||
|
foreach ($data->coveredFiles() as $relativePath) {
|
||||||
|
$data->renameFile($relativePath, $basePath.DIRECTORY_SEPARATOR.$relativePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$coverage = self::emptyCoverage();
|
||||||
|
|
||||||
|
if (! $coverage instanceof CodeCoverage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$coverage->setData($data);
|
||||||
|
$coverage->setTests($serialized['testResults']);
|
||||||
|
|
||||||
|
return $coverage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function emptyCoverage(): ?CodeCoverage
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$filter = new Filter;
|
||||||
|
|
||||||
|
return new CodeCoverage(new Selector()->forLineCoverage($filter), $filter);
|
||||||
|
} catch (Throwable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function unserializeCoverage(string $bytes): ?CodeCoverage
|
private static function unserializeCoverage(string $bytes): ?CodeCoverage
|
||||||
|
|||||||
@@ -89,6 +89,10 @@ final class FileState implements State
|
|||||||
$keys = [];
|
$keys = [];
|
||||||
|
|
||||||
foreach ($matches as $path) {
|
foreach ($matches as $path) {
|
||||||
|
if (str_ends_with($path, '.tmp')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$keys[] = basename($path);
|
$keys[] = basename($path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Pest\Plugins\Tia;
|
namespace Pest\Plugins\Tia;
|
||||||
|
|
||||||
|
use Pest\Plugins\Tia\Contracts\Lockfile;
|
||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,7 +12,14 @@ use Symfony\Component\Finder\Finder;
|
|||||||
*/
|
*/
|
||||||
final readonly class Fingerprint
|
final readonly class Fingerprint
|
||||||
{
|
{
|
||||||
private const int SCHEMA_VERSION = 17;
|
private const int SCHEMA_VERSION = 18;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<int, class-string<Lockfile>>
|
||||||
|
*/
|
||||||
|
private const array LOCKFILES = [
|
||||||
|
Lockfiles\PackageLock::class,
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array{
|
* @return array{
|
||||||
@@ -205,7 +213,11 @@ final readonly class Fingerprint
|
|||||||
$parts = [];
|
$parts = [];
|
||||||
|
|
||||||
foreach (['package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lock', 'bun.lockb'] as $name) {
|
foreach (['package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lock', 'bun.lockb'] as $name) {
|
||||||
$hash = self::trackedHash($projectRoot, $name);
|
if (! self::isTrackedByGit($projectRoot, $name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hash = self::lockfileHash($projectRoot.'/'.$name, $name);
|
||||||
|
|
||||||
if ($hash !== null) {
|
if ($hash !== null) {
|
||||||
$parts[] = $name.':'.$hash;
|
$parts[] = $name.':'.$hash;
|
||||||
@@ -215,6 +227,37 @@ final readonly class Fingerprint
|
|||||||
return $parts === [] ? null : hash('xxh128', implode("\n", $parts));
|
return $parts === [] ? null : hash('xxh128', implode("\n", $parts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function lockfileHash(string $path, string $name): ?string
|
||||||
|
{
|
||||||
|
if (! is_file($path)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = @file_get_contents($path);
|
||||||
|
|
||||||
|
if ($contents === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (self::LOCKFILES as $class) {
|
||||||
|
$handler = new $class;
|
||||||
|
|
||||||
|
if (! $handler->applies($name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fingerprint = $handler->fingerprint($contents);
|
||||||
|
|
||||||
|
if ($fingerprint !== null) {
|
||||||
|
return $fingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash('xxh128', $contents);
|
||||||
|
}
|
||||||
|
|
||||||
private static function trackedHash(string $projectRoot, string $relativePath): ?string
|
private static function trackedHash(string $projectRoot, string $relativePath): ?string
|
||||||
{
|
{
|
||||||
if (! self::isTrackedByGit($projectRoot, $relativePath)) {
|
if (! self::isTrackedByGit($projectRoot, $relativePath)) {
|
||||||
|
|||||||
@@ -149,6 +149,13 @@ final class Graph
|
|||||||
*/
|
*/
|
||||||
private function applyMigrationChanges(array $migrationPaths, array &$affectedSet): array
|
private function applyMigrationChanges(array $migrationPaths, array &$affectedSet): array
|
||||||
{
|
{
|
||||||
|
// With no recorded table usage at all, table intersection can never
|
||||||
|
// select anything — route every migration change through the
|
||||||
|
// watch-pattern fallback instead of silently skipping tests.
|
||||||
|
if ($this->testTables === []) {
|
||||||
|
return $migrationPaths;
|
||||||
|
}
|
||||||
|
|
||||||
$changedTables = [];
|
$changedTables = [];
|
||||||
$unparseable = [];
|
$unparseable = [];
|
||||||
|
|
||||||
@@ -446,13 +453,14 @@ final class Graph
|
|||||||
|
|
||||||
$bladeAffected = $this->affectedByStaticBladeUsage($rel);
|
$bladeAffected = $this->affectedByStaticBladeUsage($rel);
|
||||||
|
|
||||||
|
// Only a walk that actually selected tests counts as handled — a
|
||||||
|
// component whose usage the static walk missed must still reach
|
||||||
|
// the watch-pattern fallback instead of being silently swallowed.
|
||||||
if ($bladeAffected !== []) {
|
if ($bladeAffected !== []) {
|
||||||
foreach ($bladeAffected as $testFile) {
|
foreach ($bladeAffected as $testFile) {
|
||||||
$affectedSet[$testFile] = true;
|
$affectedSet[$testFile] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$staticallyHandled[$rel] = true;
|
|
||||||
} elseif ($this->isBladeComponentPath($rel)) {
|
|
||||||
$staticallyHandled[$rel] = true;
|
$staticallyHandled[$rel] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -690,8 +698,16 @@ final class Graph
|
|||||||
|
|
||||||
private function shouldRerun(int $status): bool
|
private function shouldRerun(int $status): bool
|
||||||
{
|
{
|
||||||
$testStatus = TestStatus::from($status);
|
return $this->shouldRerunStatus(TestStatus::from($status));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether a cached result with this status must be re-executed rather
|
||||||
|
* than replayed, honouring the configured failOn* / displayDetailsOn*
|
||||||
|
* policies.
|
||||||
|
*/
|
||||||
|
public function shouldRerunStatus(TestStatus $testStatus): bool
|
||||||
|
{
|
||||||
if ($testStatus->isFailure() || $testStatus->isError()) {
|
if ($testStatus->isFailure() || $testStatus->isError()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -813,6 +829,38 @@ final class Graph
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark test files that executed under a recorded coverage session as "known",
|
||||||
|
* seeding an empty edge set for any that produced zero project-source edges.
|
||||||
|
*
|
||||||
|
* Without this, a test that covers no application source (e.g. a pure unit
|
||||||
|
* test asserting on language primitives) never becomes an edge key, so
|
||||||
|
* {@see self::knowsTest()} reports it as unknown and it re-runs on every TIA
|
||||||
|
* run. Recording it with an empty edge set lets it be replayed/skipped while
|
||||||
|
* unchanged; it is still re-run whenever its own file changes, via
|
||||||
|
* {@see self::applyTestFileChanges()}.
|
||||||
|
*
|
||||||
|
* Must only be called from the recording path, where coverage was actually
|
||||||
|
* collected — otherwise a missing edge set could mean "coverage was off",
|
||||||
|
* not "genuinely covered nothing".
|
||||||
|
*
|
||||||
|
* @param array<int, string> $testFiles Absolute or project-relative test file paths.
|
||||||
|
*/
|
||||||
|
public function markKnownTestFiles(array $testFiles): void
|
||||||
|
{
|
||||||
|
foreach ($testFiles as $testFile) {
|
||||||
|
$rel = $this->relative($testFile);
|
||||||
|
|
||||||
|
if ($rel === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isset($this->edges[$rel])) {
|
||||||
|
$this->edges[$rel] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, array<int, string>> $testToTables
|
* @param array<string, array<int, string>> $testToTables
|
||||||
*/
|
*/
|
||||||
@@ -1217,7 +1265,21 @@ final class Graph
|
|||||||
$tail = substr($tail, 0, -strlen('.blade.php'));
|
$tail = substr($tail, 0, -strlen('.blade.php'));
|
||||||
$name = str_replace('/', '.', $tail);
|
$name = str_replace('/', '.', $tail);
|
||||||
|
|
||||||
return $name === '' ? [] : [$name, str_replace('_', '-', $name)];
|
if ($name === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$names = [$name, str_replace('_', '-', $name)];
|
||||||
|
|
||||||
|
// Anonymous index components: components/card/index.blade.php resolves as <x-card>.
|
||||||
|
if (str_ends_with($name, '.index') && $name !== '.index') {
|
||||||
|
$base = substr($name, 0, -strlen('.index'));
|
||||||
|
|
||||||
|
$names[] = $base;
|
||||||
|
$names[] = str_replace('_', '-', $base);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_unique($names));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return list<string> */
|
/** @return list<string> */
|
||||||
|
|||||||
@@ -163,7 +163,9 @@ final class JsModuleGraph
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! is_dir($projectRoot.DIRECTORY_SEPARATOR.'node_modules'.DIRECTORY_SEPARATOR.'vite')) {
|
$nodeModules = $projectRoot.DIRECTORY_SEPARATOR.'node_modules';
|
||||||
|
|
||||||
|
if (! is_dir($nodeModules.DIRECTORY_SEPARATOR.'vite') && ! is_dir($nodeModules.DIRECTORY_SEPARATOR.'rolldown')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Plugins\Tia\Lockfiles;
|
||||||
|
|
||||||
|
use Pest\Plugins\Tia\Contracts\Lockfile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final readonly class PackageLock implements Lockfile
|
||||||
|
{
|
||||||
|
public function applies(string $filename): bool
|
||||||
|
{
|
||||||
|
return $filename === 'package-lock.json';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fingerprint(string $contents): ?string
|
||||||
|
{
|
||||||
|
$data = json_decode($contents, true);
|
||||||
|
|
||||||
|
if (! is_array($data) || ! isset($data['packages']) || ! is_array($data['packages'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$packages = $data['packages'];
|
||||||
|
|
||||||
|
$platformPaths = [];
|
||||||
|
|
||||||
|
foreach ($packages as $path => $meta) {
|
||||||
|
if (is_string($path) && is_array($meta) && $this->isPlatformSpecific($meta)) {
|
||||||
|
$platformPaths[] = $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$entries = [];
|
||||||
|
|
||||||
|
foreach ($packages as $path => $meta) {
|
||||||
|
if (! is_string($path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (! is_array($meta)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($this->isPlatformSpecific($meta)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($this->isBundledUnderPlatform($path, $platformPaths)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$version = $meta['version'] ?? null;
|
||||||
|
|
||||||
|
$entries[$path] = is_string($version) ? $version : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($entries);
|
||||||
|
|
||||||
|
$encoded = json_encode($entries, JSON_UNESCAPED_SLASHES);
|
||||||
|
|
||||||
|
return $encoded === false ? null : hash('xxh128', $encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $meta
|
||||||
|
*/
|
||||||
|
private function isPlatformSpecific(array $meta): bool
|
||||||
|
{
|
||||||
|
return isset($meta['os']) || isset($meta['cpu']) || isset($meta['libc']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<string> $platformPaths
|
||||||
|
*/
|
||||||
|
private function isBundledUnderPlatform(string $path, array $platformPaths): bool
|
||||||
|
{
|
||||||
|
return array_any($platformPaths, fn (string $platformPath): bool => str_starts_with($path, $platformPath.'/node_modules/'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,6 +34,8 @@ final class Recorder
|
|||||||
|
|
||||||
private bool $active = false;
|
private bool $active = false;
|
||||||
|
|
||||||
|
private bool $captureCoverage = false;
|
||||||
|
|
||||||
private bool $driverChecked = false;
|
private bool $driverChecked = false;
|
||||||
|
|
||||||
private bool $driverAvailable = false;
|
private bool $driverAvailable = false;
|
||||||
@@ -43,6 +45,17 @@ final class Recorder
|
|||||||
private ?SourceScope $sourceScope = null;
|
private ?SourceScope $sourceScope = null;
|
||||||
|
|
||||||
public function activate(): void
|
public function activate(): void
|
||||||
|
{
|
||||||
|
$this->active = true;
|
||||||
|
$this->captureCoverage = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable per-test link tracking (tables, Inertia components, database
|
||||||
|
* usage, rendered views) without driving pcov/xdebug — for runs where
|
||||||
|
* coverage edges are piggybacked from an existing PHPUnit coverage session.
|
||||||
|
*/
|
||||||
|
public function activateLinkTracking(): void
|
||||||
{
|
{
|
||||||
$this->active = true;
|
$this->active = true;
|
||||||
}
|
}
|
||||||
@@ -75,7 +88,11 @@ final class Recorder
|
|||||||
|
|
||||||
public function beginTest(string $className, string $methodName, string $fallbackFile): void
|
public function beginTest(string $className, string $methodName, string $fallbackFile): void
|
||||||
{
|
{
|
||||||
if (! $this->active || ! $this->driverAvailable()) {
|
if (! $this->active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->captureCoverage && ! $this->driverAvailable()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +112,10 @@ final class Recorder
|
|||||||
$this->perTestUsesDatabase[$file] = true;
|
$this->perTestUsesDatabase[$file] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! $this->captureCoverage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->driver === 'pcov') {
|
if ($this->driver === 'pcov') {
|
||||||
\pcov\clear();
|
\pcov\clear();
|
||||||
\pcov\start();
|
\pcov\start();
|
||||||
@@ -107,7 +128,13 @@ final class Recorder
|
|||||||
|
|
||||||
public function endTest(): void
|
public function endTest(): void
|
||||||
{
|
{
|
||||||
if (! $this->active || ! $this->driverAvailable() || $this->currentTestFile === null) {
|
if (! $this->active || $this->currentTestFile === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->captureCoverage || ! $this->driverAvailable()) {
|
||||||
|
$this->currentTestFile = null;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +159,15 @@ final class Recorder
|
|||||||
$data = \xdebug_get_code_coverage();
|
$data = \xdebug_get_code_coverage();
|
||||||
\xdebug_stop_code_coverage(true);
|
\xdebug_stop_code_coverage(true);
|
||||||
|
|
||||||
$coveredFiles = array_keys($data);
|
$scope = $this->sourceScope();
|
||||||
|
|
||||||
|
foreach (array_keys($data) as $file) {
|
||||||
|
if (! $scope->contains($file)) {
|
||||||
|
unset($data[$file]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$coveredFiles = $this->filesWithExecutedLines($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($coveredFiles as $sourceFile) {
|
foreach ($coveredFiles as $sourceFile) {
|
||||||
@@ -351,5 +386,6 @@ final class Recorder
|
|||||||
$this->classUsesDatabaseCache = [];
|
$this->classUsesDatabaseCache = [];
|
||||||
$this->sourceScope = null;
|
$this->sourceScope = null;
|
||||||
$this->active = false;
|
$this->active = false;
|
||||||
|
$this->captureCoverage = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ namespace Pest\Plugins\Tia;
|
|||||||
*/
|
*/
|
||||||
final class TableExtractor
|
final class TableExtractor
|
||||||
{
|
{
|
||||||
private const array DML_PREFIXES = ['select', 'insert', 'update', 'delete'];
|
private const array DML_PREFIXES = ['select', 'insert', 'update', 'delete', 'with', 'replace'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single (optionally quoted) identifier segment.
|
||||||
|
*/
|
||||||
|
private const string IDENTIFIER = '(?:"[^"]+"|`[^`]+`|\[[^\]]+\]|\w+)';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return list<string> Sorted, deduped table names referenced by the
|
* @return list<string> Sorted, deduped table names referenced by the
|
||||||
@@ -22,14 +27,15 @@ final class TableExtractor
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$prefix = strtolower(substr($trimmed, 0, 6));
|
if (preg_match('/^[a-zA-Z]+/', $trimmed, $prefixMatch) !== 1) {
|
||||||
$matched = array_any(self::DML_PREFIXES, fn (string $dml): bool => str_starts_with($prefix, $dml));
|
|
||||||
|
|
||||||
if (! $matched) {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$pattern = '/(?:\bfrom|\binto|\bupdate|\bjoin)\s+(?:"([^"]+)"|`([^`]+)`|\[([^\]]+)\]|(\w+))/i';
|
if (! in_array(strtolower($prefixMatch[0]), self::DML_PREFIXES, true)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = '/\b(?:from|into|update|join)\s+('.self::IDENTIFIER.'(?:\s*\.\s*'.self::IDENTIFIER.')*)/i';
|
||||||
|
|
||||||
if (preg_match_all($pattern, $sql, $matches) === false) {
|
if (preg_match_all($pattern, $sql, $matches) === false) {
|
||||||
return [];
|
return [];
|
||||||
@@ -37,14 +43,9 @@ final class TableExtractor
|
|||||||
|
|
||||||
$tables = [];
|
$tables = [];
|
||||||
|
|
||||||
for ($i = 0, $n = count($matches[0]); $i < $n; $i++) {
|
foreach ($matches[1] as $qualified) {
|
||||||
$name = $matches[1][$i] !== ''
|
$name = self::unqualified($qualified);
|
||||||
? $matches[1][$i]
|
|
||||||
: ($matches[2][$i] !== ''
|
|
||||||
? $matches[2][$i]
|
|
||||||
: ($matches[3][$i] !== ''
|
|
||||||
? $matches[3][$i]
|
|
||||||
: $matches[4][$i]));
|
|
||||||
if ($name === '') {
|
if ($name === '') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -72,40 +73,40 @@ final class TableExtractor
|
|||||||
|
|
||||||
if (preg_match_all($schemaPattern, $php, $matches) !== false) {
|
if (preg_match_all($schemaPattern, $php, $matches) !== false) {
|
||||||
foreach ($matches[1] as $i => $primary) {
|
foreach ($matches[1] as $i => $primary) {
|
||||||
$tables[strtolower($primary)] = true;
|
$tables[strtolower(self::lastDottedSegment($primary))] = true;
|
||||||
|
|
||||||
$secondary = $matches[2][$i] ?? '';
|
$secondary = $matches[2][$i] ?? '';
|
||||||
if ($secondary !== '') {
|
if ($secondary !== '') {
|
||||||
$tables[strtolower($secondary)] = true;
|
$tables[strtolower(self::lastDottedSegment($secondary))] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$ddlPattern = '/(?:CREATE|ALTER|DROP|TRUNCATE|RENAME)\s+TABLE(?:\s+IF\s+(?:NOT\s+)?EXISTS)?\s+["`\[]?(\w+)["`\]]?/i';
|
$qualified = '('.self::IDENTIFIER.'(?:\s*\.\s*'.self::IDENTIFIER.')*)';
|
||||||
|
|
||||||
if (preg_match_all($ddlPattern, $php, $matches) !== false) {
|
$sqlPatterns = [
|
||||||
foreach ($matches[1] as $primary) {
|
'/(?:CREATE|ALTER|DROP|TRUNCATE|RENAME)\s+TABLE(?:\s+IF\s+(?:NOT\s+)?EXISTS)?\s+'.$qualified.'/i',
|
||||||
$lower = strtolower($primary);
|
'/INSERT\s+(?:IGNORE\s+)?INTO\s+'.$qualified.'/i',
|
||||||
if (! self::isSchemaMeta($lower)) {
|
'/UPDATE\s+'.$qualified.'\s+SET\b/i',
|
||||||
|
'/DELETE\s+FROM\s+'.$qualified.'/i',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($sqlPatterns as $pattern) {
|
||||||
|
if (preg_match_all($pattern, $php, $matches) === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($matches[1] as $name) {
|
||||||
|
$lower = strtolower(self::unqualified($name));
|
||||||
|
if ($lower !== '' && ! self::isSchemaMeta($lower)) {
|
||||||
$tables[$lower] = true;
|
$tables[$lower] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$dmlPatterns = [
|
if (preg_match_all('/DB::table\(\s*[\'"]([^\'"]+)[\'"]\s*\)/', $php, $matches) !== false) {
|
||||||
'/INSERT\s+(?:IGNORE\s+)?INTO\s+["`\[]?(\w+)["`\]]?/i',
|
|
||||||
'/UPDATE\s+["`\[]?(\w+)["`\]]?\s+SET\b/i',
|
|
||||||
'/DELETE\s+FROM\s+["`\[]?(\w+)["`\]]?/i',
|
|
||||||
'/DB::table\(\s*[\'"]([^\'"]+)[\'"]\s*\)/',
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($dmlPatterns as $pattern) {
|
|
||||||
if (preg_match_all($pattern, $php, $matches) === false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
foreach ($matches[1] as $name) {
|
foreach ($matches[1] as $name) {
|
||||||
$lower = strtolower($name);
|
$lower = strtolower(self::lastDottedSegment($name));
|
||||||
if (! self::isSchemaMeta($lower)) {
|
if ($lower !== '' && ! self::isSchemaMeta($lower)) {
|
||||||
$tables[$lower] = true;
|
$tables[$lower] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,6 +118,39 @@ final class TableExtractor
|
|||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The table segment of a possibly schema-qualified identifier chain,
|
||||||
|
* e.g. `"public"."users"` or `analytics.events` yield `users` / `events`.
|
||||||
|
* Empty when any segment is schema metadata (`information_schema.tables`, ...).
|
||||||
|
*/
|
||||||
|
private static function unqualified(string $qualified): string
|
||||||
|
{
|
||||||
|
$name = '';
|
||||||
|
|
||||||
|
foreach (explode('.', $qualified) as $segment) {
|
||||||
|
$segment = trim($segment, " \t\n\r\"`[]");
|
||||||
|
|
||||||
|
if ($segment === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::isSchemaMeta($segment)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $segment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function lastDottedSegment(string $name): string
|
||||||
|
{
|
||||||
|
$position = strrpos($name, '.');
|
||||||
|
|
||||||
|
return $position === false ? $name : substr($name, $position + 1);
|
||||||
|
}
|
||||||
|
|
||||||
private static function isSchemaMeta(string $name): bool
|
private static function isSchemaMeta(string $name): bool
|
||||||
{
|
{
|
||||||
$lower = strtolower($name);
|
$lower = strtolower($name);
|
||||||
|
|||||||
@@ -45,11 +45,7 @@ final readonly class TestPaths
|
|||||||
$directories[] = $rel;
|
$directories[] = $rel;
|
||||||
}
|
}
|
||||||
|
|
||||||
$suffix = $directory->suffix();
|
$suffixes[] = $directory->suffix();
|
||||||
|
|
||||||
if ($suffix !== '') {
|
|
||||||
$suffixes[] = str_starts_with($suffix, '.') ? $suffix : '.'.$suffix;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($suite->files() as $file) {
|
foreach ($suite->files() as $file) {
|
||||||
@@ -63,7 +59,7 @@ final readonly class TestPaths
|
|||||||
|
|
||||||
if ($suffixes === []) {
|
if ($suffixes === []) {
|
||||||
foreach ($configuration->testSuffixes() as $suffix) {
|
foreach ($configuration->testSuffixes() as $suffix) {
|
||||||
$suffixes[] = str_starts_with($suffix, '.') ? $suffix : '.'.$suffix;
|
$suffixes[] = $suffix;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable) {
|
} catch (Throwable) {
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ use NunoMaduro\Collision\Exceptions\TestOutcome;
|
|||||||
use PHPUnit\Event\Code\TestDoxBuilder;
|
use PHPUnit\Event\Code\TestDoxBuilder;
|
||||||
use PHPUnit\Event\Code\TestMethod;
|
use PHPUnit\Event\Code\TestMethod;
|
||||||
use PHPUnit\Event\Code\ThrowableBuilder;
|
use PHPUnit\Event\Code\ThrowableBuilder;
|
||||||
|
use PHPUnit\Event\Test\AfterLastTestMethodErrored;
|
||||||
|
use PHPUnit\Event\Test\AfterLastTestMethodFailed;
|
||||||
|
use PHPUnit\Event\Test\BeforeFirstTestMethodErrored;
|
||||||
|
use PHPUnit\Event\Test\BeforeFirstTestMethodFailed;
|
||||||
use PHPUnit\Event\Test\Errored;
|
use PHPUnit\Event\Test\Errored;
|
||||||
use PHPUnit\Event\Test\Failed;
|
use PHPUnit\Event\Test\Failed;
|
||||||
use PHPUnit\Event\Test\PhpunitDeprecationTriggered;
|
use PHPUnit\Event\Test\PhpunitDeprecationTriggered;
|
||||||
@@ -35,8 +39,7 @@ final class StateGenerator
|
|||||||
$testResultEvent->throwable()
|
$testResultEvent->throwable()
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
// @phpstan-ignore-next-line
|
$this->addClassLevelEvent($state, $testResultEvent);
|
||||||
$state->add(TestResult::fromBeforeFirstTestMethodErrored($testResultEvent));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,8 +51,7 @@ final class StateGenerator
|
|||||||
$testResultEvent->throwable()
|
$testResultEvent->throwable()
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
// @phpstan-ignore-next-line
|
$this->addClassLevelEvent($state, $testResultEvent);
|
||||||
$state->add(TestResult::fromBeforeFirstTestMethodErrored($testResultEvent));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,6 +186,36 @@ final class StateGenerator
|
|||||||
return $state;
|
return $state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given class-level "hook" failure to the state. Collision's
|
||||||
|
* `fromBeforeFirstTestMethodErrored` only accepts `BeforeFirstTestMethodErrored`
|
||||||
|
* events, so the remaining class-level events get a synthesized test method.
|
||||||
|
*/
|
||||||
|
private function addClassLevelEvent(State $state, AfterLastTestMethodErrored|AfterLastTestMethodFailed|BeforeFirstTestMethodErrored|BeforeFirstTestMethodFailed $event): void
|
||||||
|
{
|
||||||
|
if ($event instanceof BeforeFirstTestMethodErrored) {
|
||||||
|
$state->add(TestResult::fromBeforeFirstTestMethodErrored($event));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$methodName = $event instanceof BeforeFirstTestMethodFailed ? 'beforeAll' : 'afterAll';
|
||||||
|
|
||||||
|
$state->add(TestResult::fromPestParallelTestCase(
|
||||||
|
new TestMethod(
|
||||||
|
$event->testClassName(), // @phpstan-ignore-line
|
||||||
|
$methodName,
|
||||||
|
'', // @phpstan-ignore-line
|
||||||
|
1,
|
||||||
|
TestDoxBuilder::fromClassNameAndMethodName($event->testClassName(), $methodName), // @phpstan-ignore-line
|
||||||
|
MetadataCollection::fromArray([]),
|
||||||
|
TestDataCollection::fromArray([])
|
||||||
|
),
|
||||||
|
TestResult::FAIL,
|
||||||
|
$event->throwable()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, list<PhpunitDeprecationTriggered|PhpunitErrorTriggered|PhpunitNoticeTriggered|PhpunitWarningTriggered>> $testResultEvents
|
* @param array<string, list<PhpunitDeprecationTriggered|PhpunitErrorTriggered|PhpunitNoticeTriggered|PhpunitWarningTriggered>> $testResultEvents
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,18 +6,18 @@
|
|||||||
FAILED Tests\Fixtures\CollisionTest > error Exception
|
FAILED Tests\Fixtures\CollisionTest > error Exception
|
||||||
error
|
error
|
||||||
|
|
||||||
at tests/Fixtures/CollisionTest.php:4
|
at tests/Fixtures/CollisionTest.php:6
|
||||||
1▕ <?php
|
|
||||||
2▕
|
2▕
|
||||||
3▕ test('error', function () {
|
3▕ declare(strict_types=1);
|
||||||
➜ 4▕ throw new Exception('error');
|
4▕
|
||||||
5▕ })->skip(! isset($_SERVER['COLLISION_TEST']));
|
5▕ test('error', function (): void {
|
||||||
6▕
|
➜ 6▕ throw new Exception('error');
|
||||||
7▕ test('success', function () {
|
7▕ })->skip(! isset($_SERVER['COLLISION_TEST']));
|
||||||
8▕ expect(true)->toBeTrue();
|
8▕
|
||||||
9▕ })->skip(! isset($_SERVER['COLLISION_TEST']));
|
9▕ test('success', function (): void {
|
||||||
|
10▕ expect(true)->toBeTrue();
|
||||||
|
|
||||||
1 tests/Fixtures/CollisionTest.php:4
|
1 tests/Fixtures/CollisionTest.php:6
|
||||||
|
|
||||||
|
|
||||||
Tests: 1 failed, 1 passed (1 assertions)
|
Tests: 1 failed, 1 passed (1 assertions)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
Pest Testing Framework 5.0.0-rc.11.
|
Pest Testing Framework 5.0.0-beta.1.
|
||||||
|
|
||||||
USAGE: pest <file> [options]
|
USAGE: pest <file> [options]
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
--cache-directory [dir] ............................ Specify cache directory
|
--cache-directory [dir] ............................ Specify cache directory
|
||||||
--generate-configuration Generate configuration file with suggested settings
|
--generate-configuration Generate configuration file with suggested settings
|
||||||
--migrate-configuration ....... Migrate configuration file to current format
|
--migrate-configuration ....... Migrate configuration file to current format
|
||||||
|
--validate-configuration ................... Validate XML configuration file
|
||||||
--generate-baseline [file] .................... Generate baseline for issues
|
--generate-baseline [file] .................... Generate baseline for issues
|
||||||
--use-baseline [file] ........................ Use baseline to ignore issues
|
--use-baseline [file] ........................ Use baseline to ignore issues
|
||||||
--ignore-baseline ..................... Do not use baseline to ignore issues
|
--ignore-baseline ..................... Do not use baseline to ignore issues
|
||||||
@@ -40,12 +41,15 @@
|
|||||||
--uses [name] ..................... Only run tests that intend to use [name]
|
--uses [name] ..................... Only run tests that intend to use [name]
|
||||||
--requires-php-extension [name] Only run tests that require PHP extension [name]
|
--requires-php-extension [name] Only run tests that require PHP extension [name]
|
||||||
--list-test-files ................................ List available test files
|
--list-test-files ................................ List available test files
|
||||||
|
--list-test-ids ........................... List available tests as test IDs
|
||||||
--list-tests .......................................... List available tests
|
--list-tests .......................................... List available tests
|
||||||
--list-tests-xml [file] ................. List available tests in XML format
|
--list-tests-xml [file] ................. List available tests in XML format
|
||||||
--filter [pattern] ............................... Filter which tests to run
|
--filter [pattern] ............................... Filter which tests to run
|
||||||
--exclude-filter [pattern] .. Exclude tests for the specified filter pattern
|
--exclude-filter [pattern] .. Exclude tests for the specified filter pattern
|
||||||
--test-suffix [suffixes] Only search for test in files with specified suffix(es). Default: Test.php,.phpt
|
--test-suffix [suffixes] Only search for test in files with specified suffix(es). Default: Test.php,.phpt
|
||||||
--test-files-file [file] Only run test files listed in file (one file by line)
|
--test-files-file [file] Only run test files listed in file (one file by line)
|
||||||
|
--run-test-id [test-id] Only run the test identified by the specified test ID
|
||||||
|
--test-id-filter-file [file] Only run tests listed by test ID in file (one test ID per line)
|
||||||
|
|
||||||
EXECUTION OPTIONS:
|
EXECUTION OPTIONS:
|
||||||
--parallel ........................................... Run tests in parallel
|
--parallel ........................................... Run tests in parallel
|
||||||
@@ -54,20 +58,21 @@
|
|||||||
--globals-backup ................. Backup and restore $GLOBALS for each test
|
--globals-backup ................. Backup and restore $GLOBALS for each test
|
||||||
--static-backup ......... Backup and restore static properties for each test
|
--static-backup ......... Backup and restore static properties for each test
|
||||||
--strict-coverage ................... Be strict about code coverage metadata
|
--strict-coverage ................... Be strict about code coverage metadata
|
||||||
|
--require-coverage-contribution Be strict about tests that do not contribute to code coverage
|
||||||
--strict-global-state .............. Be strict about changes to global state
|
--strict-global-state .............. Be strict about changes to global state
|
||||||
--disallow-test-output ................. Be strict about output during tests
|
--disallow-test-output ................. Be strict about output during tests
|
||||||
--enforce-time-limit ................. Enforce time limit based on test size
|
--enforce-time-limit ................. Enforce time limit based on test size
|
||||||
--default-time-limit [sec] Timeout in seconds for tests that have no declared size
|
--default-time-limit [sec] Timeout in seconds for tests that have no declared size
|
||||||
--do-not-report-useless-tests Do not report tests that do not test anything
|
--do-not-report-useless-tests Do not report tests that do not test anything
|
||||||
--stop-on-defect ... Stop after first error, failure, warning, or risky test
|
--stop-on-defect[=[n]] Stop after first (or n-th) error, failure, warning, or risky test
|
||||||
--stop-on-error ..................................... Stop after first error
|
--stop-on-error[=[n]] ..................... Stop after first (or n-th) error
|
||||||
--stop-on-failure ................................. Stop after first failure
|
--stop-on-failure[=[n]] ................. Stop after first (or n-th) failure
|
||||||
--stop-on-warning ................................. Stop after first warning
|
--stop-on-warning[=[n]] ................. Stop after first (or n-th) warning
|
||||||
--stop-on-risky ................................ Stop after first risky test
|
--stop-on-risky[=[n]] ................ Stop after first (or n-th) risky test
|
||||||
--stop-on-deprecation ... Stop after first test that triggered a deprecation
|
--stop-on-deprecation[=[n]] Stop after first (or n-th) test that triggered a deprecation
|
||||||
--stop-on-notice ............. Stop after first test that triggered a notice
|
--stop-on-notice[=[n]] Stop after first (or n-th) test that triggered a notice
|
||||||
--stop-on-skipped ............................ Stop after first skipped test
|
--stop-on-skipped[=[n]] ............ Stop after first (or n-th) skipped test
|
||||||
--stop-on-incomplete ...................... Stop after first incomplete test
|
--stop-on-incomplete[=[n]] ...... Stop after first (or n-th) incomplete test
|
||||||
--fail-on-empty-test-suite Signal failure using shell exit code when no tests were run
|
--fail-on-empty-test-suite Signal failure using shell exit code when no tests were run
|
||||||
--fail-on-warning Signal failure using shell exit code when a warning was triggered
|
--fail-on-warning Signal failure using shell exit code when a warning was triggered
|
||||||
--fail-on-risky Signal failure using shell exit code when a test was considered risky
|
--fail-on-risky Signal failure using shell exit code when a test was considered risky
|
||||||
@@ -91,7 +96,7 @@
|
|||||||
--do-not-fail-on-incomplete Do not signal failure using shell exit code when a test was marked incomplete
|
--do-not-fail-on-incomplete Do not signal failure using shell exit code when a test was marked incomplete
|
||||||
--cache-result ............................ Write test results to cache file
|
--cache-result ............................ Write test results to cache file
|
||||||
--do-not-cache-result .............. Do not write test results to cache file
|
--do-not-cache-result .............. Do not write test results to cache file
|
||||||
--order-by [order] Run tests in order: default|defects|depends|duration|no-depends|random|reverse|size
|
--order-by [order] Run tests in order: default|defects|depends|duration-ascending|duration-descending|no-depends|random|reverse|size-ascending|size-descending
|
||||||
--resolve-dependencies ...................... Alias for "--order-by depends"
|
--resolve-dependencies ...................... Alias for "--order-by depends"
|
||||||
--ignore-dependencies .................... Alias for "--order-by no-depends"
|
--ignore-dependencies .................... Alias for "--order-by no-depends"
|
||||||
--random-order ............................... Alias for "--order-by random"
|
--random-order ............................... Alias for "--order-by random"
|
||||||
@@ -102,6 +107,7 @@
|
|||||||
--colors=[flag] ......... Use colors in output ("never", "auto" or "always")
|
--colors=[flag] ......... Use colors in output ("never", "auto" or "always")
|
||||||
--columns [n] ................. Number of columns to use for progress output
|
--columns [n] ................. Number of columns to use for progress output
|
||||||
--columns max ............ Use maximum number of columns for progress output
|
--columns max ............ Use maximum number of columns for progress output
|
||||||
|
--diff-context [n] Number of context lines shown around changes in diffs (default: 3)
|
||||||
--stderr ................................. Write to STDERR instead of STDOUT
|
--stderr ................................. Write to STDERR instead of STDOUT
|
||||||
--no-progress .................... Disable output of test execution progress
|
--no-progress .................... Disable output of test execution progress
|
||||||
--no-results ................................ Disable output of test results
|
--no-results ................................ Disable output of test results
|
||||||
@@ -116,6 +122,7 @@
|
|||||||
--display-warnings ......... Display details for warnings triggered by tests
|
--display-warnings ......... Display details for warnings triggered by tests
|
||||||
--display-all-issues ..... Display details for all issues that are triggered
|
--display-all-issues ..... Display details for all issues that are triggered
|
||||||
--reverse-list .............................. Print defects in reverse order
|
--reverse-list .............................. Print defects in reverse order
|
||||||
|
--compact ... Replace default progress and result output with compact format
|
||||||
--teamcity . Replace default progress and result output with TeamCity format
|
--teamcity . Replace default progress and result output with TeamCity format
|
||||||
--testdox ................ Replace default result output with TestDox format
|
--testdox ................ Replace default result output with TestDox format
|
||||||
--testdox-summary Repeat TestDox output for tests with errors, failures, or issues
|
--testdox-summary Repeat TestDox output for tests with errors, failures, or issues
|
||||||
@@ -152,8 +159,10 @@
|
|||||||
--exclude-source-from-xml-coverage Exclude [source] element from code coverage report in XML format
|
--exclude-source-from-xml-coverage Exclude [source] element from code coverage report in XML format
|
||||||
--warm-coverage-cache ........................... Warm static analysis cache
|
--warm-coverage-cache ........................... Warm static analysis cache
|
||||||
--coverage-filter [dir] ........... Include [dir] in code coverage reporting
|
--coverage-filter [dir] ........... Include [dir] in code coverage reporting
|
||||||
--path-coverage .......... Report path coverage in addition to line coverage
|
--branch-coverage ...... Report branch coverage in addition to line coverage
|
||||||
|
--path-coverage ... Report path coverage in addition to line/branch coverage
|
||||||
--disable-coverage-ignore ...... Disable metadata for ignoring code coverage
|
--disable-coverage-ignore ...... Disable metadata for ignoring code coverage
|
||||||
|
--disable-coverage-targeting .. Disable metadata for code coverage targeting
|
||||||
--no-coverage Ignore code coverage reporting configured in the XML configuration file
|
--no-coverage Ignore code coverage reporting configured in the XML configuration file
|
||||||
|
|
||||||
AI OPTIONS:
|
AI OPTIONS:
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
|
|
||||||
Pest Testing Framework 5.0.0-rc.11.
|
Pest Testing Framework 5.0.0-beta.1.
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,11 @@
|
|||||||
✓ describe()->with() preserves depends → first with (9)
|
✓ describe()->with() preserves depends → first with (9)
|
||||||
✓ describe()->with() preserves depends → second with (9)
|
✓ describe()->with() preserves depends → second with (9)
|
||||||
|
|
||||||
|
PASS Tests\Features\DatasetProviderErrors
|
||||||
|
✓ reports missing datasets as errors for a single file run
|
||||||
|
✓ reports missing datasets as errors alongside passing tests
|
||||||
|
✓ reports dataset closure exceptions as errors
|
||||||
|
|
||||||
PASS Tests\Features\DatasetsTests - 1 todo
|
PASS Tests\Features\DatasetsTests - 1 todo
|
||||||
✓ it throws exception if dataset does not exist
|
✓ it throws exception if dataset does not exist
|
||||||
✓ it throws exception if dataset already exist
|
✓ it throws exception if dataset already exist
|
||||||
@@ -293,8 +298,8 @@
|
|||||||
✓ it uses correct parent class
|
✓ it uses correct parent class
|
||||||
|
|
||||||
DEPR Tests\Features\Deprecated
|
DEPR Tests\Features\Deprecated
|
||||||
! deprecated → str_contains(): Passing null to parameter #1 ($haystack) of type string is deprecated // tests/Features/Deprecated.php:4
|
✓ deprecated
|
||||||
! user deprecated → Since foo 1.0: This is a deprecation description // tests/Features/Deprecated.php:10
|
! user deprecated → Since foo 1.0: This is a deprecation description // tests/Features/Deprecated.php:12
|
||||||
|
|
||||||
PASS Tests\Features\Describe - 5 todos
|
PASS Tests\Features\Describe - 5 todos
|
||||||
✓ before each
|
✓ before each
|
||||||
@@ -471,6 +476,16 @@
|
|||||||
✓ pass
|
✓ pass
|
||||||
✓ failures
|
✓ failures
|
||||||
✓ failures with custom message
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeBase64
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with invalid padding
|
||||||
|
✓ failures with padding only
|
||||||
|
✓ failures with malformed input
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
✓ not failures
|
✓ not failures
|
||||||
|
|
||||||
PASS Tests\Features\Expect\toBeBetween
|
PASS Tests\Features\Expect\toBeBetween
|
||||||
@@ -517,6 +532,14 @@
|
|||||||
✓ pass
|
✓ pass
|
||||||
✓ failures
|
✓ failures
|
||||||
✓ failures with custom message
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeDomain
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with leading dot
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
✓ not failures
|
✓ not failures
|
||||||
|
|
||||||
PASS Tests\Features\Expect\toBeEmail
|
PASS Tests\Features\Expect\toBeEmail
|
||||||
@@ -579,6 +602,24 @@
|
|||||||
✓ passes with strings
|
✓ passes with strings
|
||||||
✓ failures
|
✓ failures
|
||||||
✓ failures with custom message
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeHexadecimal
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with invalid characters
|
||||||
|
✓ failures with zero-x prefix
|
||||||
|
✓ failures with empty string
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeHostname
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with trailing hyphen
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
✓ not failures
|
✓ not failures
|
||||||
|
|
||||||
PASS Tests\Features\Expect\toBeIn
|
PASS Tests\Features\Expect\toBeIn
|
||||||
@@ -617,6 +658,13 @@
|
|||||||
✓ failure when the class is not invokable
|
✓ failure when the class is not invokable
|
||||||
✓ class is not invokable
|
✓ class is not invokable
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeIpAddress
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
PASS Tests\Features\Expect\toBeIterable
|
PASS Tests\Features\Expect\toBeIterable
|
||||||
✓ pass
|
✓ pass
|
||||||
✓ failures
|
✓ failures
|
||||||
@@ -661,6 +709,13 @@
|
|||||||
✓ pass
|
✓ pass
|
||||||
✓ failures
|
✓ failures
|
||||||
✓ failures with custom message
|
✓ failures with custom message
|
||||||
|
✓ not failures
|
||||||
|
|
||||||
|
PASS Tests\Features\Expect\toBeMacAddress
|
||||||
|
✓ pass
|
||||||
|
✓ failures
|
||||||
|
✓ failures with invalid type
|
||||||
|
✓ failures with custom message
|
||||||
✓ not failures
|
✓ not failures
|
||||||
|
|
||||||
PASS Tests\Features\Expect\toBeNAN
|
PASS Tests\Features\Expect\toBeNAN
|
||||||
@@ -1274,8 +1329,8 @@
|
|||||||
// This is another runtime note
|
// This is another runtime note
|
||||||
|
|
||||||
NOTI Tests\Features\Notices
|
NOTI Tests\Features\Notices
|
||||||
! notice → This is a notice description // tests/Features/Notices.php:4
|
! notice → This is a notice description // tests/Features/Notices.php:6
|
||||||
! a "describe" group of tests → notice → This is a notice description // tests/Features/Notices.php:11
|
! a "describe" group of tests → notice → This is a notice description // tests/Features/Notices.php:13
|
||||||
|
|
||||||
PASS Tests\Features\Pr
|
PASS Tests\Features\Pr
|
||||||
✓ it may be associated with an pr #1, #2
|
✓ it may be associated with an pr #1, #2
|
||||||
@@ -1816,10 +1871,135 @@
|
|||||||
✓ output format → it returns a 32-character hex xxh128 hash
|
✓ output format → it returns a 32-character hex xxh128 hash
|
||||||
✓ output format → it returns a stable hash for empty content
|
✓ output format → it returns a stable hash for empty content
|
||||||
|
|
||||||
|
PASS Tests\Unit\Plugins\Tia\FileState
|
||||||
|
✓ keysWithPrefix() → it lists keys matching the prefix
|
||||||
|
✓ keysWithPrefix() → it ignores in-flight temporary files from concurrent writes
|
||||||
|
|
||||||
|
PASS Tests\Unit\Plugins\Tia\Graph
|
||||||
|
✓ shouldRerunStatus() → it re-runs failures and errors, replays successes
|
||||||
|
✓ applyMigrationChanges() → it selects tests whose recorded tables intersect the changed migration
|
||||||
|
✓ applyMigrationChanges() → it falls back to watch patterns when no table usage was recorded at all
|
||||||
|
✓ rerun tracking → it reruns cached failures via their file
|
||||||
|
✓ rerun tracking → it flags cached failures whose file is unknown
|
||||||
|
✓ applyBladeStaticChanges() → it maps an anonymous index component to the views that render it
|
||||||
|
✓ applyBladeStaticChanges() → it falls back to watch patterns for components with no matched usage
|
||||||
|
✓ markKnownTestFiles() → it makes a test file with no edges known
|
||||||
|
✓ markKnownTestFiles() → it does not clobber edges of an already-known test file
|
||||||
|
✓ markKnownTestFiles() → it ignores paths outside the project root
|
||||||
|
|
||||||
PASS Tests\Unit\Plugins\Tia\IsEnabledForRun
|
PASS Tests\Unit\Plugins\Tia\IsEnabledForRun
|
||||||
✓ does not throw when an integer --random-order-seed is passed as a separate argv element
|
✓ does not throw when an integer --random-order-seed is passed as a separate argv element
|
||||||
✓ still detects --tia when an integer argument is present
|
✓ still detects --tia when an integer argument is present
|
||||||
|
|
||||||
|
PASS Tests\Unit\Plugins\Tia\Lockfiles\PackageLock
|
||||||
|
✓ it applies only to package-lock.json
|
||||||
|
✓ it returns null for contents that are not an npm lockfile
|
||||||
|
✓ it is stable when the directory-derived top-level name changes
|
||||||
|
✓ it ignores platform-specific native binaries added or dropped per OS
|
||||||
|
✓ it ignores libc-constrained variants
|
||||||
|
✓ it ignores runtime deps bundled beneath a platform-specific package
|
||||||
|
✓ it detects a real dependency version change
|
||||||
|
✓ it detects an added or removed non-platform dependency
|
||||||
|
✓ it is independent of package ordering
|
||||||
|
|
||||||
|
PASS Tests\Unit\Plugins\Tia\Recorder
|
||||||
|
✓ activateLinkTracking() → it tracks tables without a coverage driver
|
||||||
|
✓ activateLinkTracking() → it tracks inertia components without a coverage driver
|
||||||
|
✓ activateLinkTracking() → it tracks linked sources across consecutive tests
|
||||||
|
✓ activateLinkTracking() → it records nothing while inactive
|
||||||
|
|
||||||
|
PASS Tests\Unit\Plugins\Tia\TableExtractor
|
||||||
|
✓ fromSql() → it extracts tables from plain DML
|
||||||
|
✓ fromSql() → it extracts tables from joins
|
||||||
|
✓ fromSql() → it extracts tables from CTE queries
|
||||||
|
✓ fromSql() → it extracts tables from REPLACE INTO
|
||||||
|
✓ fromSql() → it records the table, not the schema, for qualified identifiers
|
||||||
|
✓ fromSql() → it handles quoted identifiers
|
||||||
|
✓ fromSql() → it ignores schema metadata tables
|
||||||
|
✓ fromSql() → it returns nothing for non-DML statements
|
||||||
|
✓ fromMigrationSource() → it extracts tables from Schema builder calls
|
||||||
|
✓ fromMigrationSource() → it extracts tables from raw DDL statements
|
||||||
|
✓ fromMigrationSource() → it records the table, not the schema, in qualified DDL and DML
|
||||||
|
✓ fromMigrationSource() → it extracts tables from DB::table calls
|
||||||
|
|
||||||
|
PASS Tests\Unit\Plugins\Tia\TestPaths
|
||||||
|
✓ isTestFile() → it matches a standard test file under a configured directory
|
||||||
|
✓ isTestFile() → it does not match a non-test file that shares the directory
|
||||||
|
✓ isTestFile() → it does not match files outside the configured directories
|
||||||
|
✓ isTestFile() → it matches an explicitly listed file
|
||||||
|
✓ isTestFile() → it honours a bare .php suffix
|
||||||
|
✓ isTestFile() → it does not require a dot before the suffix
|
||||||
|
|
||||||
|
PASS Tests\Unit\Plugins\Tia\ViteDepsHelper
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('plain-object')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('plain-array')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('nested')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('empty-object')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('empty-array')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('line-comment-own-line')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('line-comment-trailing')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('leading-line-comments')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('line-comment-eof-no-newline')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('block-comment-leading')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('block-comment-inline')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('block-comment-multiline')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('block-comment-eof')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('jsdoc-style-block')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('block-contains-double-slash')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('line-then-block')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('comment-with-quotes')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('comment-with-braces-commas')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('trailing-comma-object')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('trailing-comma-array')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('trailing-comma-nested')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('trailing-comma-newline')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('trailing-comma-then-block')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('trailing-comma-then-line')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('crlf-line-comment')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('tabs-whitespace')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('url-in-string')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('glob-in-string')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('path-alias-in-string')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('block-open-in-string')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('block-close-in-string')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('block-both-in-string')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('star-in-string')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('single-slashes-in-string')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('escaped-quote-in-string')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('escaped-backslash-in-string')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('backslash-then-slash')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('newline-escape-in-string')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('unicode-in-string')
|
||||||
|
✓ it strips JSONC down to something JSON.parse accepts with ('block-secret')
|
||||||
|
✓ it never touches comment-looking sequences inside string values
|
||||||
|
✓ it removes the comment body entirely
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('basic')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('multiple-aliases')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('default-baseUrl')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('baseUrl-subdir')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('jsconfig-fallback')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('first-target-wins')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('multiple-keys-mixed')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('no-paths')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('no-compiler-options')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('key-without-glob')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('target-without-glob')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('target-not-array')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('target-empty-array')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('commented-tsconfig')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('trailing-comma-paths')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('malformed-json')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('missing-config')
|
||||||
|
✓ it builds the expected alias map from a tsconfig with ('tsconfig-precedence')
|
||||||
|
✓ it builds the expected alias map from a vite config with ('root-slash-literal')
|
||||||
|
✓ it builds the expected alias map from a vite config with ('path-resolve')
|
||||||
|
✓ it builds the expected alias map from a vite config with ('file-url')
|
||||||
|
✓ it builds the expected alias map from a vite config with ('tilde-alias')
|
||||||
|
✓ it builds the expected alias map from a vite config with ('multiple-aliases')
|
||||||
|
✓ it builds the expected alias map from a vite config with ('laravel-plugin-default')
|
||||||
|
✓ it builds the expected alias map from a vite config with ('no-alias-no-plugin')
|
||||||
|
✓ it builds the expected alias map from a vite config with ('no-config')
|
||||||
|
|
||||||
PASS Tests\Unit\Preset
|
PASS Tests\Unit\Preset
|
||||||
✓ preset invalid name
|
✓ preset invalid name
|
||||||
✓ preset → myFramework
|
✓ preset → myFramework
|
||||||
@@ -2006,4 +2186,4 @@
|
|||||||
✓ pass with dataset with ('my-datas-set-value')
|
✓ pass with dataset with ('my-datas-set-value')
|
||||||
✓ within describe → pass with dataset with ('my-datas-set-value')
|
✓ within describe → pass with dataset with ('my-datas-set-value')
|
||||||
|
|
||||||
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1389 passed (3097 assertions)
|
Tests: 1 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1542 passed (3375 assertions)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
dataset('throws', function () {
|
||||||
|
throw new RuntimeException('boom from dataset');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('x', function ($a) {
|
||||||
|
expect($a)->toBeTrue();
|
||||||
|
})->with('throws');
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
it('references a missing dataset', function ($value) {
|
||||||
|
expect($value)->toBeTruthy();
|
||||||
|
})->with('missing');
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
it('passes normally', function () {
|
||||||
|
expect(true)->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('references a missing dataset', function ($value) {
|
||||||
|
expect($value)->toBeTruthy();
|
||||||
|
})->with('missing');
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Pest\Expectation;
|
use Pest\Expectation;
|
||||||
use Pest\Plugins\Tia\BaselineSync;
|
use Pest\Plugins\Tia\BaselineSync;
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -1,12 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Pest\Plugin;
|
use Pest\Plugin;
|
||||||
|
|
||||||
trait PluginTrait
|
trait PluginTrait
|
||||||
{
|
{
|
||||||
public function assertPluginTraitGotRegistered(): void
|
public function assertPluginTraitGotRegistered(): void
|
||||||
{
|
{
|
||||||
$this->assertTrue(true);
|
expect(true)->toBeTrue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,14 +16,14 @@ trait SecondPluginTrait
|
|||||||
{
|
{
|
||||||
public function assertSecondPluginTraitGotRegistered(): void
|
public function assertSecondPluginTraitGotRegistered(): void
|
||||||
{
|
{
|
||||||
$this->assertTrue(true);
|
expect(true)->toBeTrue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Plugin::uses(PluginTrait::class);
|
Plugin::uses(PluginTrait::class);
|
||||||
Plugin::uses(SecondPluginTrait::class);
|
Plugin::uses(SecondPluginTrait::class);
|
||||||
|
|
||||||
function _assertThat()
|
function _assertThat(): void
|
||||||
{
|
{
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
dataset('bound.closure', function () {
|
dataset('bound.closure', function () {
|
||||||
yield function () {
|
yield fn (): int => 1;
|
||||||
return 1;
|
yield fn (): int => 2;
|
||||||
};
|
|
||||||
yield function () {
|
|
||||||
return 2;
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
dataset('bound.array', [
|
dataset('bound.array', [
|
||||||
function () {
|
fn (): int => 1,
|
||||||
return 1;
|
fn (): int => 2,
|
||||||
},
|
|
||||||
function () {
|
|
||||||
return 2;
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
dataset('numbers.closure', function () {
|
dataset('numbers.closure', function () {
|
||||||
yield [1];
|
yield [1];
|
||||||
yield [2];
|
yield [2];
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
test('global functions are loaded', function () {
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
test('global functions are loaded', function (): void {
|
||||||
expect(helper_returns_string())->toBeString();
|
expect(helper_returns_string())->toBeString();
|
||||||
});
|
});
|
||||||
|
|||||||
+25
-25
@@ -1,10 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->count = 0;
|
$this->count = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function (): void {
|
||||||
match ($this->name()) {
|
match ($this->name()) {
|
||||||
'__pest_evaluable_it_can_run_after_test' => expect($this->count)->toBe(1),
|
'__pest_evaluable_it_can_run_after_test' => expect($this->count)->toBe(1),
|
||||||
'__pest_evaluable_it_can_run_after_test_twice' => expect($this->count)->toBe(1),
|
'__pest_evaluable_it_can_run_after_test_twice' => expect($this->count)->toBe(1),
|
||||||
@@ -21,37 +21,37 @@ afterEach(function () {
|
|||||||
$this->count++;
|
$this->count++;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can run after test', function () {
|
it('can run after test', function (): void {
|
||||||
expect($this->count)->toBe(0);
|
expect($this->count)->toBe(0);
|
||||||
|
|
||||||
$this->count++;
|
$this->count++;
|
||||||
})->after(function () {
|
})->after(function (): void {
|
||||||
expect($this->count)->toBe(2);
|
expect($this->count)->toBe(2);
|
||||||
|
|
||||||
$this->count++;
|
$this->count++;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can run after test twice', function () {
|
it('can run after test twice', function (): void {
|
||||||
expect($this->count)->toBe(0);
|
expect($this->count)->toBe(0);
|
||||||
|
|
||||||
$this->count++;
|
$this->count++;
|
||||||
})->after(function () {
|
})->after(function (): void {
|
||||||
expect($this->count)->toBe(2);
|
expect($this->count)->toBe(2);
|
||||||
|
|
||||||
$this->count++;
|
$this->count++;
|
||||||
})->after(function () {
|
})->after(function (): void {
|
||||||
expect($this->count)->toBe(3);
|
expect($this->count)->toBe(3);
|
||||||
|
|
||||||
$this->count++;
|
$this->count++;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not run when skipped', function () {
|
it('does not run when skipped', function (): void {
|
||||||
dd('This should not run 1');
|
dd('This should not run 1');
|
||||||
})->skip()->after(function () {
|
})->skip()->after(function (): void {
|
||||||
dd('This should not run 2');
|
dd('This should not run 2');
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function (): void {
|
||||||
match ($this->name()) {
|
match ($this->name()) {
|
||||||
'__pest_evaluable_it_can_run_after_test' => expect($this->count)->toBe(3),
|
'__pest_evaluable_it_can_run_after_test' => expect($this->count)->toBe(3),
|
||||||
'__pest_evaluable_it_can_run_after_test_twice' => expect($this->count)->toBe(4),
|
'__pest_evaluable_it_can_run_after_test_twice' => expect($this->count)->toBe(4),
|
||||||
@@ -70,7 +70,7 @@ afterEach(function () {
|
|||||||
$this->count++;
|
$this->count++;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function (): void {
|
||||||
match ($this->name()) {
|
match ($this->name()) {
|
||||||
'__pest_evaluable_it_can_run_after_test' => expect($this->count)->toBe(4),
|
'__pest_evaluable_it_can_run_after_test' => expect($this->count)->toBe(4),
|
||||||
'__pest_evaluable_it_can_run_after_test_twice' => expect($this->count)->toBe(5),
|
'__pest_evaluable_it_can_run_after_test_twice' => expect($this->count)->toBe(5),
|
||||||
@@ -87,47 +87,47 @@ afterEach(function () {
|
|||||||
$this->count++;
|
$this->count++;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('something', function () {
|
describe('something', function (): void {
|
||||||
it('does not run when skipped', function () {
|
it('does not run when skipped', function (): void {
|
||||||
dd('This should not run 3');
|
dd('This should not run 3');
|
||||||
})->skip()->after(function () {
|
})->skip()->after(function (): void {
|
||||||
dd('This should not run 4');
|
dd('This should not run 4');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can run after test', function () {
|
it('can run after test', function (): void {
|
||||||
expect($this->count)->toBe(0);
|
expect($this->count)->toBe(0);
|
||||||
|
|
||||||
$this->count++;
|
$this->count++;
|
||||||
})->after(function () {
|
})->after(function (): void {
|
||||||
expect($this->count)->toBe(5);
|
expect($this->count)->toBe(5);
|
||||||
|
|
||||||
$this->count++;
|
$this->count++;
|
||||||
})->after(function () {
|
})->after(function (): void {
|
||||||
expect($this->count)->toBe(6);
|
expect($this->count)->toBe(6);
|
||||||
|
|
||||||
$this->count++;
|
$this->count++;
|
||||||
});
|
});
|
||||||
})->after(function () {
|
})->after(function (): void {
|
||||||
expect($this->count)->toBe(4);
|
expect($this->count)->toBe(4);
|
||||||
|
|
||||||
$this->count++;
|
$this->count++;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('something 2', function () {
|
describe('something 2', function (): void {
|
||||||
it('can run after test', function () {
|
it('can run after test', function (): void {
|
||||||
expect($this->count)->toBe(0);
|
expect($this->count)->toBe(0);
|
||||||
|
|
||||||
$this->count++;
|
$this->count++;
|
||||||
})->after(function () {
|
})->after(function (): void {
|
||||||
expect($this->count)->toBe(6);
|
expect($this->count)->toBe(6);
|
||||||
|
|
||||||
$this->count++;
|
$this->count++;
|
||||||
});
|
});
|
||||||
})->after(function () {
|
})->after(function (): void {
|
||||||
expect($this->count)->toBe(4);
|
expect($this->count)->toBe(4);
|
||||||
|
|
||||||
$this->count++;
|
$this->count++;
|
||||||
})->after(function () {
|
})->after(function (): void {
|
||||||
expect($this->count)->toBe(5);
|
expect($this->count)->toBe(5);
|
||||||
|
|
||||||
$this->count++;
|
$this->count++;
|
||||||
@@ -136,7 +136,7 @@ describe('something 2', function () {
|
|||||||
test('high order test')
|
test('high order test')
|
||||||
->defer(fn () => $this->count++)
|
->defer(fn () => $this->count++)
|
||||||
->expect(fn () => $this->count)->toBe(1)
|
->expect(fn () => $this->count)->toBe(1)
|
||||||
->after(function () {
|
->after(function (): void {
|
||||||
expect($this->count)->toBe(4);
|
expect($this->count)->toBe(4);
|
||||||
|
|
||||||
$this->count++;
|
$this->count++;
|
||||||
@@ -146,7 +146,7 @@ test('high order test with skip')
|
|||||||
->skip()
|
->skip()
|
||||||
->defer(fn () => $this->count++)
|
->defer(fn () => $this->count++)
|
||||||
->expect(fn () => $this->count)->toBe(1)
|
->expect(fn () => $this->count)->toBe(1)
|
||||||
->after(function () {
|
->after(function (): void {
|
||||||
dd('This should not run 5');
|
dd('This should not run 5');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
$file = __DIR__.DIRECTORY_SEPARATOR.'after-all-test';
|
$file = __DIR__.DIRECTORY_SEPARATOR.'after-all-test';
|
||||||
|
|
||||||
beforeAll(function () use ($file) {
|
beforeAll(function () use ($file): void {
|
||||||
@unlink($file);
|
@unlink($file);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(function () use ($file) {
|
afterAll(function () use ($file): void {
|
||||||
@unlink($file);
|
@unlink($file);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('deletes file after all', function () use ($file) {
|
test('deletes file after all', function () use ($file): void {
|
||||||
file_put_contents($file, 'foo');
|
file_put_contents($file, 'foo');
|
||||||
$this->assertFileExists($file);
|
expect($file)->toBeFile();
|
||||||
register_shutdown_function(function () {
|
register_shutdown_function(function (): void {
|
||||||
// $this->assertFileDoesNotExist($file);
|
// $this->assertFileDoesNotExist($file);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,99 +2,99 @@
|
|||||||
|
|
||||||
$state = new stdClass;
|
$state = new stdClass;
|
||||||
|
|
||||||
beforeEach(function () use ($state) {
|
beforeEach(function () use ($state): void {
|
||||||
$this->state = $state;
|
$this->state = $state;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function (): void {
|
||||||
$this->state->bar = 1;
|
$this->state->bar = 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function (): void {
|
||||||
unset($this->state->bar);
|
unset($this->state->bar);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not get executed before the test', function () {
|
it('does not get executed before the test', function (): void {
|
||||||
expect($this->state)->not->toHaveProperty('bar');
|
expect($this->state)->not->toHaveProperty('bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets executed after the test', function () {
|
it('gets executed after the test', function (): void {
|
||||||
expect($this->state)->toHaveProperty('bar');
|
expect($this->state)->toHaveProperty('bar')
|
||||||
expect($this->state->bar)->toBe(2);
|
->and($this->state->bar)->toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function (): void {
|
||||||
$this->state->bar = 2;
|
$this->state->bar = 2;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('outer', function () {
|
describe('outer', function (): void {
|
||||||
afterEach(function () {
|
afterEach(function (): void {
|
||||||
$this->state->bar++;
|
$this->state->bar++;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inner', function () {
|
describe('inner', function (): void {
|
||||||
afterEach(function () {
|
afterEach(function (): void {
|
||||||
$this->state->bar++;
|
$this->state->bar++;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not get executed before the test', function () {
|
it('does not get executed before the test', function (): void {
|
||||||
expect($this->state)->toHaveProperty('bar');
|
expect($this->state)->toHaveProperty('bar')
|
||||||
expect($this->state->bar)->toBe(2);
|
->and($this->state->bar)->toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call all parent afterEach functions', function () {
|
it('should call all parent afterEach functions', function (): void {
|
||||||
expect($this->state)->toHaveProperty('bar');
|
expect($this->state)->toHaveProperty('bar')
|
||||||
expect($this->state->bar)->toBe(4);
|
->and($this->state->bar)->toBe(4);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('matching describe block names', function () {
|
describe('matching describe block names', function (): void {
|
||||||
afterEach(function () {
|
afterEach(function (): void {
|
||||||
$this->state->foo = 1;
|
$this->state->foo = 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('outer', function () {
|
describe('outer', function (): void {
|
||||||
afterEach(function () {
|
afterEach(function (): void {
|
||||||
$this->state->foo++;
|
$this->state->foo++;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('middle', function () {
|
describe('middle', function (): void {
|
||||||
afterEach(function () {
|
afterEach(function (): void {
|
||||||
$this->state->foo++;
|
$this->state->foo++;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inner', function () {
|
describe('inner', function (): void {
|
||||||
afterEach(function () {
|
afterEach(function (): void {
|
||||||
$this->state->foo++;
|
$this->state->foo++;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not get executed before the test', function () {
|
it('does not get executed before the test', function (): void {
|
||||||
expect($this)->not->toHaveProperty('foo');
|
expect($this)->not->toHaveProperty('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call all parent afterEach functions', function () {
|
it('should call all parent afterEach functions', function (): void {
|
||||||
expect($this->state->foo)->toBe(4);
|
expect($this->state->foo)->toBe(4);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('middle', function () {
|
describe('middle', function (): void {
|
||||||
it('does not get executed before the test', function () {
|
it('does not get executed before the test', function (): void {
|
||||||
expect($this)->not->toHaveProperty('foo');
|
expect($this)->not->toHaveProperty('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not call afterEach functions for sibling describe blocks with the same name', function () {
|
it('should not call afterEach functions for sibling describe blocks with the same name', function (): void {
|
||||||
expect($this)->not->toHaveProperty('foo');
|
expect($this)->not->toHaveProperty('foo');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inner', function () {
|
describe('inner', function (): void {
|
||||||
it('does not get executed before the test', function () {
|
it('does not get executed before the test', function (): void {
|
||||||
expect($this)->not->toHaveProperty('foo');
|
expect($this)->not->toHaveProperty('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not call afterEach functions for descendent of sibling describe blocks with the same name', function () {
|
it('should not call afterEach functions for descendent of sibling describe blocks with the same name', function (): void {
|
||||||
expect($this)->not->toHaveProperty('foo');
|
expect($this)->not->toHaveProperty('foo');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
})->assignee('nunomaduro');
|
})->assignee('nunomaduro');
|
||||||
|
|
||||||
it('may be associated with an assignee', function () {
|
it('may be associated with an assignee', function (): void {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
})->assignee('taylorotwell');
|
})->assignee('taylorotwell');
|
||||||
|
|
||||||
describe('nested', function () {
|
describe('nested', function (): void {
|
||||||
it('may be associated with an assignee', function () {
|
it('may be associated with an assignee', function (): void {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
})->assignee('taylorotwell');
|
})->assignee('taylorotwell');
|
||||||
})->assignee('nunomaduro')->note('an note between an the assignee')->assignee(['jamesbrooks', 'joedixon']);
|
})->assignee('nunomaduro')->note('an note between an the assignee')->assignee(['jamesbrooks', 'joedixon']);
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
$foo = new stdClass;
|
$foo = new stdClass;
|
||||||
$foo->bar = 0;
|
$foo->bar = 0;
|
||||||
|
|
||||||
beforeAll(function () use ($foo) {
|
beforeAll(function () use ($foo): void {
|
||||||
$foo->bar++;
|
$foo->bar++;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets executed before tests', function () use ($foo) {
|
it('gets executed before tests', function () use ($foo): void {
|
||||||
expect($foo)->bar->toBe(1);
|
expect($foo)->bar->toBe(1);
|
||||||
|
|
||||||
$foo->bar = 'changed';
|
$foo->bar = 'changed';
|
||||||
});
|
});
|
||||||
|
|
||||||
it('do not get executed before each test', function () use ($foo) {
|
it('do not get executed before each test', function () use ($foo): void {
|
||||||
expect($foo)->bar->toBe('changed');
|
expect($foo)->bar->toBe('changed');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,91 +1,91 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->bar = 2;
|
$this->bar = 2;
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->bar++;
|
$this->bar++;
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->bar = 0;
|
$this->bar = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets executed before each test', function () {
|
it('gets executed before each test', function (): void {
|
||||||
expect($this->bar)->toBe(1);
|
expect($this->bar)->toBe(1);
|
||||||
|
|
||||||
$this->bar = 'changed';
|
$this->bar = 'changed';
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets executed before each test once again', function () {
|
it('gets executed before each test once again', function (): void {
|
||||||
expect($this->bar)->toBe(1);
|
expect($this->bar)->toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->bar++;
|
$this->bar++;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('outer', function () {
|
describe('outer', function (): void {
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->bar++;
|
$this->bar++;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inner', function () {
|
describe('inner', function (): void {
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->bar++;
|
$this->bar++;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call all parent beforeEach functions', function () {
|
it('should call all parent beforeEach functions', function (): void {
|
||||||
expect($this->bar)->toBe(3);
|
expect($this->bar)->toBe(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with expectations', function () {
|
describe('with expectations', function (): void {
|
||||||
beforeEach()->expect(true)->toBeTrue();
|
beforeEach()->expect(true)->toBeTrue();
|
||||||
|
|
||||||
describe('nested block', function () {
|
describe('nested block', function (): void {
|
||||||
test('test', function () {});
|
test('test', function (): void {});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('test', function () {});
|
test('test', function (): void {});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('matching describe block names', function () {
|
describe('matching describe block names', function (): void {
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->foo = 1;
|
$this->foo = 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('outer', function () {
|
describe('outer', function (): void {
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->foo++;
|
$this->foo++;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('middle', function () {
|
describe('middle', function (): void {
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->foo++;
|
$this->foo++;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inner', function () {
|
describe('inner', function (): void {
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->foo++;
|
$this->foo++;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call all parent beforeEach functions', function () {
|
it('should call all parent beforeEach functions', function (): void {
|
||||||
expect($this->foo)->toBe(4);
|
expect($this->foo)->toBe(4);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('middle', function () {
|
describe('middle', function (): void {
|
||||||
it('should not call beforeEach functions for sibling describe blocks with the same name', function () {
|
it('should not call beforeEach functions for sibling describe blocks with the same name', function (): void {
|
||||||
expect($this->foo)->toBe(2);
|
expect($this->foo)->toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inner', function () {
|
describe('inner', function (): void {
|
||||||
it('should not call beforeEach functions for descendent of sibling describe blocks with the same name', function () {
|
it('should not call beforeEach functions for descendent of sibling describe blocks with the same name', function (): void {
|
||||||
expect($this->foo)->toBe(2);
|
expect($this->foo)->toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -93,36 +93,36 @@ describe('matching describe block names', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$matchingNameCalls = 0;
|
$matchingNameCalls = 0;
|
||||||
describe('matching name', function () use (&$matchingNameCalls) {
|
describe('matching name', function () use (&$matchingNameCalls): void {
|
||||||
beforeEach(function () use (&$matchingNameCalls) {
|
beforeEach(function () use (&$matchingNameCalls): void {
|
||||||
$matchingNameCalls++;
|
$matchingNameCalls++;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call the before each', function () use (&$matchingNameCalls) {
|
it('should call the before each', function () use (&$matchingNameCalls): void {
|
||||||
expect($matchingNameCalls)->toBe(1);
|
expect($matchingNameCalls)->toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('matching name', function () use (&$matchingNameCalls) {
|
describe('matching name', function () use (&$matchingNameCalls): void {
|
||||||
it('should not call the before each on the describe block with the same name', function () use (&$matchingNameCalls) {
|
it('should not call the before each on the describe block with the same name', function () use (&$matchingNameCalls): void {
|
||||||
expect($matchingNameCalls)->toBe(1);
|
expect($matchingNameCalls)->toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->baz = 1;
|
$this->baz = 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('called on all tests', function () {
|
describe('called on all tests', function (): void {
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->baz++;
|
$this->baz++;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('beforeEach should be called', function () {
|
test('beforeEach should be called', function (): void {
|
||||||
expect($this->baz)->toBe(2);
|
expect($this->baz)->toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('beforeEach should be called for all tests', function () {
|
test('beforeEach should be called for all tests', function (): void {
|
||||||
expect($this->baz)->toBe(2);
|
expect($this->baz)->toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
beforeEach()->expect(true)->toBeTrue();
|
beforeEach()->expect(true)->toBeTrue();
|
||||||
|
|
||||||
test('runs 1', function () {
|
test('runs 1', function (): void {
|
||||||
// This test did performs assertions...
|
// This test did performs assertions...
|
||||||
});
|
});
|
||||||
|
|
||||||
test('runs 2', function () {
|
test('runs 2', function (): void {
|
||||||
// This test did performs assertions...
|
// This test did performs assertions...
|
||||||
});
|
});
|
||||||
|
|
||||||
test('runs 3', function () {
|
test('runs 3', function (): void {
|
||||||
// This test did performs assertions...
|
// This test did performs assertions...
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
beforeEach()->skip();
|
beforeEach()->skip();
|
||||||
|
|
||||||
test('does not run 1', function () {
|
test('does not run 1', function (): void {
|
||||||
$this->fail('This test should not run');
|
$this->fail('This test should not run');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('does not run 2', function () {
|
test('does not run 2', function (): void {
|
||||||
$this->fail('This test should not run');
|
$this->fail('This test should not run');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('does not run 3', function () {
|
test('does not run 3', function (): void {
|
||||||
$this->fail('This test should not run');
|
$this->fail('This test should not run');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
beforeEach()->todo();
|
beforeEach()->todo();
|
||||||
|
|
||||||
test('is marked as todo 1', function () {
|
test('is marked as todo 1', function (): void {
|
||||||
$this->fail('This test should not run');
|
$this->fail('This test should not run');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('is marked as todo 2', function () {
|
test('is marked as todo 2', function (): void {
|
||||||
$this->fail('This test should not run');
|
$this->fail('This test should not run');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ use Symfony\Component\Console\Output\ConsoleOutput;
|
|||||||
|
|
||||||
it('has plugin')->assertTrue(class_exists(CoveragePlugin::class));
|
it('has plugin')->assertTrue(class_exists(CoveragePlugin::class));
|
||||||
|
|
||||||
it('adds coverage if --coverage exist', function () {
|
it('adds coverage if --coverage exist', function (): void {
|
||||||
$plugin = new CoveragePlugin(new ConsoleOutput);
|
$plugin = new CoveragePlugin(new ConsoleOutput);
|
||||||
|
|
||||||
expect($plugin->coverage)->toBeFalse();
|
expect($plugin->coverage)->toBeFalse();
|
||||||
$arguments = $plugin->handleArguments([]);
|
$arguments = $plugin->handleArguments([]);
|
||||||
expect($arguments)->toEqual([])
|
expect($arguments)->toBeEmpty()
|
||||||
->and($plugin->coverage)->toBeFalse();
|
->and($plugin->coverage)->toBeFalse();
|
||||||
|
|
||||||
$arguments = $plugin->handleArguments(['--coverage']);
|
$arguments = $plugin->handleArguments(['--coverage']);
|
||||||
@@ -19,7 +19,7 @@ it('adds coverage if --coverage exist', function () {
|
|||||||
->and($plugin->coverage)->toBeTrue();
|
->and($plugin->coverage)->toBeTrue();
|
||||||
})->skip(! Coverage::isAvailable() || ! function_exists('xdebug_info') || ! in_array('coverage', xdebug_info('mode'), true), 'Coverage is not available');
|
})->skip(! Coverage::isAvailable() || ! function_exists('xdebug_info') || ! in_array('coverage', xdebug_info('mode'), true), 'Coverage is not available');
|
||||||
|
|
||||||
it('adds coverage if --min exist', function () {
|
it('adds coverage if --min exist', function (): void {
|
||||||
$plugin = new CoveragePlugin(new ConsoleOutput);
|
$plugin = new CoveragePlugin(new ConsoleOutput);
|
||||||
expect($plugin->coverageMin)->toEqual(0.0)
|
expect($plugin->coverageMin)->toEqual(0.0)
|
||||||
->and($plugin->coverage)->toBeFalse();
|
->and($plugin->coverage)->toBeFalse();
|
||||||
@@ -34,7 +34,7 @@ it('adds coverage if --min exist', function () {
|
|||||||
expect($plugin->coverageMin)->toEqual(2.4);
|
expect($plugin->coverageMin)->toEqual(2.4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('generates coverage based on file input', function () {
|
it('generates coverage based on file input', function (): void {
|
||||||
expect(Coverage::getMissingCoverage(new class
|
expect(Coverage::getMissingCoverage(new class
|
||||||
{
|
{
|
||||||
public function lineCoverageData(): array
|
public function lineCoverageData(): array
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ use Tests\Fixtures\Covers\CoversClass1;
|
|||||||
|
|
||||||
covers([CoversClass1::class]);
|
covers([CoversClass1::class]);
|
||||||
|
|
||||||
it('uses the correct PHPUnit attribute for class', function () {
|
it('uses the correct PHPUnit attribute for class', function (): void {
|
||||||
$attributes = (new ReflectionClass($this))->getAttributes();
|
$attributes = new ReflectionClass($this)->getAttributes();
|
||||||
|
|
||||||
expect($attributes[1]->getName())->toBe(CoversClass::class);
|
expect($attributes[1]->getName())->toBe(CoversClass::class)
|
||||||
expect($attributes[1]->getArguments()[0])->toBe('Tests\Fixtures\Covers\CoversClass1');
|
->and($attributes[1]->getArguments()[0])->toBe(CoversClass1::class);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Pest\PendingCalls\TestCall;
|
use Pest\PendingCalls\TestCall;
|
||||||
use Pest\TestSuite;
|
use Pest\TestSuite;
|
||||||
|
|
||||||
it('throws exception if no class nor method has been found', function () {
|
it('throws exception if no class nor method has been found', function (): void {
|
||||||
$testCall = new TestCall(TestSuite::getInstance(), 'filename', 'description', fn () => 'closure');
|
$testCall = new TestCall(TestSuite::getInstance(), 'filename', 'description', fn () => 'closure');
|
||||||
|
|
||||||
$testCall->covers('fakeName');
|
$testCall->covers('fakeName');
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
use PHPUnit\Framework\Attributes\CoversFunction;
|
use PHPUnit\Framework\Attributes\CoversFunction;
|
||||||
|
|
||||||
function testCoversFunction() {}
|
function testCoversFunction(): void {}
|
||||||
|
|
||||||
it('uses the correct PHPUnit attribute for function', function () {
|
it('uses the correct PHPUnit attribute for function', function (): void {
|
||||||
$attributes = (new ReflectionClass($this))->getAttributes();
|
$attributes = new ReflectionClass($this)->getAttributes();
|
||||||
|
|
||||||
expect($attributes[1]->getName())->toBe(CoversFunction::class);
|
expect($attributes[1]->getName())->toBe(CoversFunction::class)
|
||||||
expect($attributes[1]->getArguments()[0])->toBe('testCoversFunction');
|
->and($attributes[1]->getArguments()[0])->toBe('testCoversFunction');
|
||||||
})->coversFunction('testCoversFunction');
|
})->coversFunction('testCoversFunction');
|
||||||
|
|||||||
@@ -4,14 +4,13 @@ use PHPUnit\Framework\Attributes\CoversClass;
|
|||||||
use PHPUnit\Framework\Attributes\CoversFunction;
|
use PHPUnit\Framework\Attributes\CoversFunction;
|
||||||
use Tests\Fixtures\Covers\CoversClass3;
|
use Tests\Fixtures\Covers\CoversClass3;
|
||||||
|
|
||||||
function testCoversFunction2() {}
|
function testCoversFunction2(): void {}
|
||||||
|
|
||||||
it('guesses if the given argument is a class or function', function () {
|
it('guesses if the given argument is a class or function', function (): void {
|
||||||
$attributes = (new ReflectionClass($this))->getAttributes();
|
$attributes = new ReflectionClass($this)->getAttributes();
|
||||||
|
|
||||||
expect($attributes[1]->getName())->toBe(CoversClass::class);
|
expect($attributes[1]->getName())->toBe(CoversClass::class)
|
||||||
expect($attributes[1]->getArguments()[0])->toBe(CoversClass3::class);
|
->and($attributes[1]->getArguments()[0])->toBe(CoversClass3::class)
|
||||||
|
->and($attributes[2]->getName())->toBe(CoversFunction::class)
|
||||||
expect($attributes[2]->getName())->toBe(CoversFunction::class);
|
->and($attributes[2]->getArguments()[0])->toBe('testCoversFunction2');
|
||||||
expect($attributes[2]->getArguments()[0])->toBe('testCoversFunction2');
|
|
||||||
})->covers(CoversClass3::class, 'testCoversFunction2');
|
})->covers(CoversClass3::class, 'testCoversFunction2');
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
use PHPUnit\Framework\Attributes\CoversTrait as PHPUnitCoversTrait;
|
use PHPUnit\Framework\Attributes\CoversTrait as PHPUnitCoversTrait;
|
||||||
use Tests\Fixtures\Covers\CoversTrait;
|
use Tests\Fixtures\Covers\CoversTrait;
|
||||||
|
|
||||||
it('uses the correct PHPUnit attribute for trait', function () {
|
it('uses the correct PHPUnit attribute for trait', function (): void {
|
||||||
$attributes = (new ReflectionClass($this))->getAttributes();
|
$attributes = new ReflectionClass($this)->getAttributes();
|
||||||
|
|
||||||
expect($attributes[1]->getName())->toBe(PHPUnitCoversTrait::class);
|
expect($attributes[1]->getName())->toBe(PHPUnitCoversTrait::class)
|
||||||
expect($attributes[1]->getArguments()[0])->toBe('Tests\Fixtures\Covers\CoversTrait');
|
->and($attributes[1]->getArguments()[0])->toBe(CoversTrait::class);
|
||||||
})->coversTrait(CoversTrait::class);
|
})->coversTrait(CoversTrait::class);
|
||||||
|
|||||||
@@ -11,41 +11,41 @@
|
|||||||
// beforeEach()->with() inside describe blocks
|
// beforeEach()->with() inside describe blocks
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
describe('beforeEach()->with() applies dataset to tests', function () {
|
describe('beforeEach()->with() applies dataset to tests', function (): void {
|
||||||
beforeEach()->with([10]);
|
beforeEach()->with([10]);
|
||||||
|
|
||||||
test('receives the dataset value', function ($value) {
|
test('receives the dataset value', function ($value): void {
|
||||||
expect($value)->toBe(10);
|
expect($value)->toBe(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('also receives the dataset value in it()', function ($value) {
|
it('also receives the dataset value in it()', function ($value): void {
|
||||||
expect($value)->toBe(10);
|
expect($value)->toBe(10);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('beforeEach()->with() with multiple dataset values', function () {
|
describe('beforeEach()->with() with multiple dataset values', function (): void {
|
||||||
beforeEach()->with([1, 2, 3]);
|
beforeEach()->with([1, 2, 3]);
|
||||||
|
|
||||||
test('receives each value from the dataset', function ($value) {
|
test('receives each value from the dataset', function ($value): void {
|
||||||
expect($value)->toBeIn([1, 2, 3]);
|
expect($value)->toBeIn([1, 2, 3]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('beforeEach()->with() with keyed dataset', function () {
|
describe('beforeEach()->with() with keyed dataset', function (): void {
|
||||||
beforeEach()->with(['first' => [10], 'second' => [20]]);
|
beforeEach()->with(['first' => [10], 'second' => [20]]);
|
||||||
|
|
||||||
test('receives keyed dataset values', function ($value) {
|
test('receives keyed dataset values', function ($value): void {
|
||||||
expect($value)->toBeIn([10, 20]);
|
expect($value)->toBeIn([10, 20]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('beforeEach()->with() with closure dataset', function () {
|
describe('beforeEach()->with() with closure dataset', function (): void {
|
||||||
beforeEach()->with(function () {
|
beforeEach()->with(function () {
|
||||||
yield [100];
|
yield [100];
|
||||||
yield [200];
|
yield [200];
|
||||||
});
|
});
|
||||||
|
|
||||||
test('receives values from closure dataset', function ($value) {
|
test('receives values from closure dataset', function ($value): void {
|
||||||
expect($value)->toBeIn([100, 200]);
|
expect($value)->toBeIn([100, 200]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -54,30 +54,30 @@ describe('beforeEach()->with() with closure dataset', function () {
|
|||||||
// describe()->with() method chaining
|
// describe()->with() method chaining
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
describe('describe()->with() passes dataset to tests', function () {
|
describe('describe()->with() passes dataset to tests', function (): void {
|
||||||
test('receives the dataset value', function ($value) {
|
test('receives the dataset value', function ($value): void {
|
||||||
expect($value)->toBe(42);
|
expect($value)->toBe(42);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('also receives it in it()', function ($value) {
|
it('also receives it in it()', function ($value): void {
|
||||||
expect($value)->toBe(42);
|
expect($value)->toBe(42);
|
||||||
});
|
});
|
||||||
})->with([42]);
|
})->with([42]);
|
||||||
|
|
||||||
describe('describe()->with() with multiple values', function () {
|
describe('describe()->with() with multiple values', function (): void {
|
||||||
test('receives each value', function ($value) {
|
test('receives each value', function ($value): void {
|
||||||
expect($value)->toBeIn([5, 10, 15]);
|
expect($value)->toBeIn([5, 10, 15]);
|
||||||
});
|
});
|
||||||
})->with([5, 10, 15]);
|
})->with([5, 10, 15]);
|
||||||
|
|
||||||
describe('describe()->with() with keyed dataset', function () {
|
describe('describe()->with() with keyed dataset', function (): void {
|
||||||
test('receives keyed values', function ($value) {
|
test('receives keyed values', function ($value): void {
|
||||||
expect($value)->toBeIn([100, 200]);
|
expect($value)->toBeIn([100, 200]);
|
||||||
});
|
});
|
||||||
})->with(['alpha' => [100], 'beta' => [200]]);
|
})->with(['alpha' => [100], 'beta' => [200]]);
|
||||||
|
|
||||||
describe('describe()->with() with closure dataset', function () {
|
describe('describe()->with() with closure dataset', function (): void {
|
||||||
test('receives closure dataset values', function ($value) {
|
test('receives closure dataset values', function ($value): void {
|
||||||
expect($value)->toBeIn([7, 14]);
|
expect($value)->toBeIn([7, 14]);
|
||||||
});
|
});
|
||||||
})->with(function () {
|
})->with(function () {
|
||||||
@@ -89,33 +89,33 @@ describe('describe()->with() with closure dataset', function () {
|
|||||||
// Nested describe blocks with datasets
|
// Nested describe blocks with datasets
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
describe('outer with dataset', function () {
|
describe('outer with dataset', function (): void {
|
||||||
describe('inner without dataset', function () {
|
describe('inner without dataset', function (): void {
|
||||||
test('inherits outer dataset', function (...$args) {
|
test('inherits outer dataset', function (...$args): void {
|
||||||
expect($args)->toBe([1]);
|
expect($args)->toBe([1]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})->with([1]);
|
})->with([1]);
|
||||||
|
|
||||||
describe('nested describe blocks with datasets at multiple levels', function () {
|
describe('nested describe blocks with datasets at multiple levels', function (): void {
|
||||||
describe('level 1', function () {
|
describe('level 1', function (): void {
|
||||||
test('receives level 1 dataset', function (...$args) {
|
test('receives level 1 dataset', function (...$args): void {
|
||||||
expect($args)->toBe([10]);
|
expect($args)->toBe([10]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('level 2', function () {
|
describe('level 2', function (): void {
|
||||||
test('receives datasets from all ancestor levels', function (...$args) {
|
test('receives datasets from all ancestor levels', function (...$args): void {
|
||||||
expect($args)->toBe([10, 20]);
|
expect($args)->toBe([10, 20]);
|
||||||
});
|
});
|
||||||
})->with([20]);
|
})->with([20]);
|
||||||
})->with([10]);
|
})->with([10]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deeply nested describe with datasets', function () {
|
describe('deeply nested describe with datasets', function (): void {
|
||||||
describe('a', function () {
|
describe('a', function (): void {
|
||||||
describe('b', function () {
|
describe('b', function (): void {
|
||||||
describe('c', function () {
|
describe('c', function (): void {
|
||||||
test('receives all ancestor datasets', function (...$args) {
|
test('receives all ancestor datasets', function (...$args): void {
|
||||||
expect($args)->toBe([1, 2, 3]);
|
expect($args)->toBe([1, 2, 3]);
|
||||||
});
|
});
|
||||||
})->with([3]);
|
})->with([3]);
|
||||||
@@ -127,19 +127,19 @@ describe('deeply nested describe with datasets', function () {
|
|||||||
// Combining hook datasets with test-level datasets
|
// Combining hook datasets with test-level datasets
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
describe('beforeEach()->with() combined with test->with()', function () {
|
describe('beforeEach()->with() combined with test->with()', function (): void {
|
||||||
beforeEach()->with([10]);
|
beforeEach()->with([10]);
|
||||||
|
|
||||||
test('receives both datasets as cross product', function ($hookValue, $testValue) {
|
test('receives both datasets as cross product', function ($hookValue, $testValue): void {
|
||||||
expect($hookValue)->toBe(10);
|
expect($hookValue)->toBe(10)
|
||||||
expect($testValue)->toBeIn([1, 2]);
|
->and($testValue)->toBeIn([1, 2]);
|
||||||
})->with([1, 2]);
|
})->with([1, 2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('describe()->with() combined with test->with()', function () {
|
describe('describe()->with() combined with test->with()', function (): void {
|
||||||
test('receives both datasets', function ($describeValue, $testValue) {
|
test('receives both datasets', function ($describeValue, $testValue): void {
|
||||||
expect($describeValue)->toBe(5);
|
expect($describeValue)->toBe(5)
|
||||||
expect($testValue)->toBeIn([50, 60]);
|
->and($testValue)->toBeIn([50, 60]);
|
||||||
})->with([50, 60]);
|
})->with([50, 60]);
|
||||||
})->with([5]);
|
})->with([5]);
|
||||||
|
|
||||||
@@ -147,33 +147,33 @@ describe('describe()->with() combined with test->with()', function () {
|
|||||||
// beforeEach()->with() combined with beforeEach closure
|
// beforeEach()->with() combined with beforeEach closure
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
describe('beforeEach closure and beforeEach()->with() coexist', function () {
|
describe('beforeEach closure and beforeEach()->with() coexist', function (): void {
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->setupValue = 'initialized';
|
$this->setupValue = 'initialized';
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach()->with([99]);
|
beforeEach()->with([99]);
|
||||||
|
|
||||||
test('has both the closure state and dataset', function ($value) {
|
test('has both the closure state and dataset', function ($value): void {
|
||||||
expect($this->setupValue)->toBe('initialized');
|
expect($this->setupValue)->toBe('initialized')
|
||||||
expect($value)->toBe(99);
|
->and($value)->toBe(99);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('beforeEach()->with() does not interfere with closure hooks', function () {
|
describe('beforeEach()->with() does not interfere with closure hooks', function (): void {
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->counter = 1;
|
$this->counter = 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->counter++;
|
$this->counter++;
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach()->with([42]);
|
beforeEach()->with([42]);
|
||||||
|
|
||||||
test('closures run in order and dataset is applied', function ($value) {
|
test('closures run in order and dataset is applied', function ($value): void {
|
||||||
expect($this->counter)->toBe(2);
|
expect($this->counter)->toBe(2)
|
||||||
expect($value)->toBe(42);
|
->and($value)->toBe(42);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -181,24 +181,24 @@ describe('beforeEach()->with() does not interfere with closure hooks', function
|
|||||||
// Dataset isolation between describe blocks
|
// Dataset isolation between describe blocks
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
describe('first describe with dataset', function () {
|
describe('first describe with dataset', function (): void {
|
||||||
beforeEach()->with([111]);
|
beforeEach()->with([111]);
|
||||||
|
|
||||||
test('gets its own dataset', function ($value) {
|
test('gets its own dataset', function ($value): void {
|
||||||
expect($value)->toBe(111);
|
expect($value)->toBe(111);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('second describe with different dataset', function () {
|
describe('second describe with different dataset', function (): void {
|
||||||
beforeEach()->with([222]);
|
beforeEach()->with([222]);
|
||||||
|
|
||||||
test('gets its own dataset, not the sibling', function ($value) {
|
test('gets its own dataset, not the sibling', function ($value): void {
|
||||||
expect($value)->toBe(222);
|
expect($value)->toBe(222);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('third describe without dataset', function () {
|
describe('third describe without dataset', function (): void {
|
||||||
test('has no dataset leaking from siblings', function () {
|
test('has no dataset leaking from siblings', function (): void {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -207,23 +207,23 @@ describe('third describe without dataset', function () {
|
|||||||
// describe()->with() combined with beforeEach hooks
|
// describe()->with() combined with beforeEach hooks
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
describe('describe()->with() with beforeEach closure', function () {
|
describe('describe()->with() with beforeEach closure', function (): void {
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->hookRan = true;
|
$this->hookRan = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('both hook and dataset work', function ($value) {
|
test('both hook and dataset work', function ($value): void {
|
||||||
expect($this->hookRan)->toBeTrue();
|
expect($this->hookRan)->toBeTrue()
|
||||||
expect($value)->toBe(77);
|
->and($value)->toBe(77);
|
||||||
});
|
});
|
||||||
})->with([77]);
|
})->with([77]);
|
||||||
|
|
||||||
describe('describe()->with() with afterEach closure', function () {
|
describe('describe()->with() with afterEach closure', function (): void {
|
||||||
afterEach(function () {
|
afterEach(function (): void {
|
||||||
expect($this->value)->toBe(88);
|
expect($this->value)->toBe(88);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('dataset is available and afterEach runs', function ($value) {
|
test('dataset is available and afterEach runs', function ($value): void {
|
||||||
$this->value = $value;
|
$this->value = $value;
|
||||||
expect($value)->toBe(88);
|
expect($value)->toBe(88);
|
||||||
});
|
});
|
||||||
@@ -233,18 +233,18 @@ describe('describe()->with() with afterEach closure', function () {
|
|||||||
// Multiple tests in a describe with beforeEach()->with()
|
// Multiple tests in a describe with beforeEach()->with()
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
describe('multiple tests share the same beforeEach dataset', function () {
|
describe('multiple tests share the same beforeEach dataset', function (): void {
|
||||||
beforeEach()->with([33]);
|
beforeEach()->with([33]);
|
||||||
|
|
||||||
test('first test gets the dataset', function ($value) {
|
test('first test gets the dataset', function ($value): void {
|
||||||
expect($value)->toBe(33);
|
expect($value)->toBe(33);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('second test also gets the dataset', function ($value) {
|
test('second test also gets the dataset', function ($value): void {
|
||||||
expect($value)->toBe(33);
|
expect($value)->toBe(33);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('third test with it() also gets the dataset', function ($value) {
|
it('third test with it() also gets the dataset', function ($value): void {
|
||||||
expect($value)->toBe(33);
|
expect($value)->toBe(33);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -253,21 +253,21 @@ describe('multiple tests share the same beforeEach dataset', function () {
|
|||||||
// Nested describe with beforeEach()->with() at inner level
|
// Nested describe with beforeEach()->with() at inner level
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
describe('outer describe', function () {
|
describe('outer describe', function (): void {
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->outer = true;
|
$this->outer = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inner describe with dataset on hook', function () {
|
describe('inner describe with dataset on hook', function (): void {
|
||||||
beforeEach()->with([55]);
|
beforeEach()->with([55]);
|
||||||
|
|
||||||
test('inherits outer beforeEach and has inner dataset', function ($value) {
|
test('inherits outer beforeEach and has inner dataset', function ($value): void {
|
||||||
expect($this->outer)->toBeTrue();
|
expect($this->outer)->toBeTrue()
|
||||||
expect($value)->toBe(55);
|
->and($value)->toBe(55);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('outer test is unaffected by inner dataset', function () {
|
test('outer test is unaffected by inner dataset', function (): void {
|
||||||
expect($this->outer)->toBeTrue();
|
expect($this->outer)->toBeTrue();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -276,12 +276,12 @@ describe('outer describe', function () {
|
|||||||
// describe()->with() with depends
|
// describe()->with() with depends
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
describe('describe()->with() preserves depends', function () {
|
describe('describe()->with() preserves depends', function (): void {
|
||||||
test('first', function ($value) {
|
test('first', function ($value): void {
|
||||||
expect($value)->toBe(9);
|
expect($value)->toBe(9);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('second', function ($value) {
|
test('second', function ($value): void {
|
||||||
expect($value)->toBe(9);
|
expect($value)->toBe(9);
|
||||||
})->depends('first');
|
})->depends('first');
|
||||||
})->with([9]);
|
})->with([9]);
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
$run = function (string $target): array {
|
||||||
|
$process = new Process(
|
||||||
|
['php', 'bin/pest', $target],
|
||||||
|
dirname(__DIR__, 2),
|
||||||
|
['COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true', 'PAO_DISABLE' => '1'],
|
||||||
|
);
|
||||||
|
|
||||||
|
$process->run();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'output' => removeAnsiEscapeSequences($process->getOutput().$process->getErrorOutput()),
|
||||||
|
'code' => $process->getExitCode(),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
test('reports missing datasets as errors for a single file run', function () use ($run): void {
|
||||||
|
$result = $run('tests/.tests/IssueOnly.php');
|
||||||
|
|
||||||
|
expect($result['output'])
|
||||||
|
->toContain("A dataset with the name `missing` does not exist. You can create it using `dataset('missing', ['a', 'b']);`.")
|
||||||
|
->toContain('FAILED')
|
||||||
|
->toContain('Tests: 1 failed')
|
||||||
|
->and($result['code'])->not->toBe(0);
|
||||||
|
})->skipOnWindows();
|
||||||
|
|
||||||
|
test('reports missing datasets as errors alongside passing tests', function () use ($run): void {
|
||||||
|
$result = $run('tests/.tests/IssueWithPassing.php');
|
||||||
|
|
||||||
|
expect($result['output'])
|
||||||
|
->toContain("A dataset with the name `missing` does not exist. You can create it using `dataset('missing', ['a', 'b']);`.")
|
||||||
|
->toContain('1 passed')
|
||||||
|
->toContain('1 failed')
|
||||||
|
->and($result['code'])->not->toBe(0);
|
||||||
|
})->skipOnWindows();
|
||||||
|
|
||||||
|
test('reports dataset closure exceptions as errors', function () use ($run): void {
|
||||||
|
$result = $run('tests/.tests/DatasetClosureThrows.php');
|
||||||
|
|
||||||
|
expect($result['output'])
|
||||||
|
->toContain('boom from dataset')
|
||||||
|
->and($result['code'])->not->toBe(0);
|
||||||
|
})->skipOnWindows();
|
||||||
+142
-189
@@ -5,25 +5,20 @@ use Pest\Exceptions\DatasetDoesNotExist;
|
|||||||
use Pest\Plugin;
|
use Pest\Plugin;
|
||||||
use Pest\Repositories\DatasetsRepository;
|
use Pest\Repositories\DatasetsRepository;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->foo = 'bar';
|
$this->foo = 'bar';
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws exception if dataset does not exist', function () {
|
it('throws exception if dataset does not exist', function (): void {
|
||||||
$this->expectException(DatasetDoesNotExist::class);
|
expect(fn () => DatasetsRepository::resolve(['first'], __FILE__))->toThrow(DatasetDoesNotExist::class, "A dataset with the name `first` does not exist. You can create it using `dataset('first', ['a', 'b']);`.");
|
||||||
$this->expectExceptionMessage("A dataset with the name `first` does not exist. You can create it using `dataset('first', ['a', 'b']);`.");
|
|
||||||
|
|
||||||
DatasetsRepository::resolve(['first'], __FILE__);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws exception if dataset already exist', function () {
|
it('throws exception if dataset already exist', function (): void {
|
||||||
DatasetsRepository::set('second', [[]], __DIR__);
|
|
||||||
$this->expectException(DatasetAlreadyExists::class);
|
|
||||||
$this->expectExceptionMessage('A dataset with the name `second` already exists in scope ['.__DIR__.'].');
|
|
||||||
DatasetsRepository::set('second', [[]], __DIR__);
|
DatasetsRepository::set('second', [[]], __DIR__);
|
||||||
|
expect(fn () => DatasetsRepository::set('second', [[]], __DIR__))->toThrow(DatasetAlreadyExists::class, 'A dataset with the name `second` already exists in scope ['.__DIR__.'].');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets closures', function () {
|
it('sets closures', function (): void {
|
||||||
DatasetsRepository::set('foo', function () {
|
DatasetsRepository::set('foo', function () {
|
||||||
yield [1];
|
yield [1];
|
||||||
}, __DIR__);
|
}, __DIR__);
|
||||||
@@ -31,18 +26,18 @@ it('sets closures', function () {
|
|||||||
expect(DatasetsRepository::resolve(['foo'], __FILE__))->toBe(['(1)' => [1]]);
|
expect(DatasetsRepository::resolve(['foo'], __FILE__))->toBe(['(1)' => [1]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets arrays', function () {
|
it('sets arrays', function (): void {
|
||||||
DatasetsRepository::set('bar', [[2]], __DIR__);
|
DatasetsRepository::set('bar', [[2]], __DIR__);
|
||||||
|
|
||||||
expect(DatasetsRepository::resolve(['bar'], __FILE__))->toBe(['(2)' => [2]]);
|
expect(DatasetsRepository::resolve(['bar'], __FILE__))->toBe(['(2)' => [2]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets bound to test case object', function ($value) {
|
it('gets bound to test case object', function ($value): void {
|
||||||
$this->assertTrue(true);
|
expect(true)->toBeTrue();
|
||||||
})->with([['a'], ['b']]);
|
})->with([['a'], ['b']]);
|
||||||
|
|
||||||
test('it truncates the description', function () {
|
test('it truncates the description', function (): void {
|
||||||
expect(true)->toBe(true);
|
expect(true)->toBeTrue();
|
||||||
// it gets tested by the integration test
|
// it gets tested by the integration test
|
||||||
})->with([str_repeat('Fooo', 10)]);
|
})->with([str_repeat('Fooo', 10)]);
|
||||||
|
|
||||||
@@ -51,61 +46,59 @@ $state->text = '';
|
|||||||
|
|
||||||
$datasets = [[1], [2]];
|
$datasets = [[1], [2]];
|
||||||
|
|
||||||
test('lazy datasets', function ($text) use ($state, $datasets) {
|
test('lazy datasets', function ($text) use ($state, $datasets): void {
|
||||||
$state->text .= $text;
|
$state->text .= $text;
|
||||||
|
|
||||||
expect(in_array([$text], $datasets))->toBe(true);
|
expect([$text])->toBeIn($datasets);
|
||||||
})->with($datasets);
|
})->with($datasets);
|
||||||
|
|
||||||
test('lazy datasets did the job right', function () use ($state) {
|
test('lazy datasets did the job right', function () use ($state): void {
|
||||||
expect($state->text)->toBe('12');
|
expect($state->text)->toBe('12');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('interpolated :dataset lazy datasets', function ($text) {
|
test('interpolated :dataset lazy datasets', function ($text): void {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
})->with($datasets);
|
})->with($datasets);
|
||||||
|
|
||||||
$state->text = '';
|
$state->text = '';
|
||||||
|
|
||||||
test('eager datasets', function ($text) use ($state, $datasets) {
|
test('eager datasets', function ($text) use ($state, $datasets): void {
|
||||||
$state->text .= $text;
|
$state->text .= $text;
|
||||||
expect($datasets)->toContain([$text]);
|
expect($datasets)->toContain([$text]);
|
||||||
})->with(function () use ($datasets) {
|
})->with(fn (): array => $datasets);
|
||||||
return $datasets;
|
|
||||||
});
|
|
||||||
|
|
||||||
test('eager datasets did the job right', function () use ($state) {
|
test('eager datasets did the job right', function () use ($state): void {
|
||||||
expect($state->text)->toBe('1212');
|
expect($state->text)->toBe('1212');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('lazy registered datasets', function ($text) use ($state, $datasets) {
|
test('lazy registered datasets', function ($text) use ($state, $datasets): void {
|
||||||
$state->text .= $text;
|
$state->text .= $text;
|
||||||
expect($datasets)->toContain([$text]);
|
expect($datasets)->toContain([$text]);
|
||||||
})->with('numbers.array');
|
})->with('numbers.array');
|
||||||
|
|
||||||
test('lazy registered datasets did the job right', function () use ($state) {
|
test('lazy registered datasets did the job right', function () use ($state): void {
|
||||||
expect($state->text)->toBe('121212');
|
expect($state->text)->toBe('121212');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('eager registered datasets', function ($text) use ($state, $datasets) {
|
test('eager registered datasets', function ($text) use ($state, $datasets): void {
|
||||||
$state->text .= $text;
|
$state->text .= $text;
|
||||||
expect($datasets)->toContain([$text]);
|
expect($datasets)->toContain([$text]);
|
||||||
})->with('numbers.closure');
|
})->with('numbers.closure');
|
||||||
|
|
||||||
test('eager registered datasets did the job right', function () use ($state) {
|
test('eager registered datasets did the job right', function () use ($state): void {
|
||||||
expect($state->text)->toBe('12121212');
|
expect($state->text)->toBe('12121212');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('eager wrapped registered datasets', function ($text) use ($state, $datasets) {
|
test('eager wrapped registered datasets', function ($text) use ($state, $datasets): void {
|
||||||
$state->text .= $text;
|
$state->text .= $text;
|
||||||
expect($datasets)->toContain([$text]);
|
expect($datasets)->toContain([$text]);
|
||||||
})->with('numbers.closure.wrapped');
|
})->with('numbers.closure.wrapped');
|
||||||
|
|
||||||
test('eager registered wrapped datasets did the job right', function () use ($state) {
|
test('eager registered wrapped datasets did the job right', function () use ($state): void {
|
||||||
expect($state->text)->toBe('1212121212');
|
expect($state->text)->toBe('1212121212');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('named datasets', function ($text) use ($state, $datasets) {
|
test('named datasets', function ($text) use ($state, $datasets): void {
|
||||||
$state->text .= $text;
|
$state->text .= $text;
|
||||||
expect($datasets)->toContain([$text]);
|
expect($datasets)->toContain([$text]);
|
||||||
})->with([
|
})->with([
|
||||||
@@ -113,14 +106,14 @@ test('named datasets', function ($text) use ($state, $datasets) {
|
|||||||
'two' => [2],
|
'two' => [2],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('interpolated :dataset named datasets', function ($text) {
|
test('interpolated :dataset named datasets', function ($text): void {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
})->with([
|
})->with([
|
||||||
'one' => [1],
|
'one' => [1],
|
||||||
'two' => [2],
|
'two' => [2],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('named datasets did the job right', function () use ($state) {
|
test('named datasets did the job right', function () use ($state): void {
|
||||||
expect($state->text)->toBe('121212121212');
|
expect($state->text)->toBe('121212121212');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -133,13 +126,13 @@ $namedDatasets = [
|
|||||||
new Bar,
|
new Bar,
|
||||||
];
|
];
|
||||||
|
|
||||||
test('lazy named datasets', function ($text) {
|
test('lazy named datasets', function ($text): void {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
})->with($namedDatasets);
|
})->with($namedDatasets);
|
||||||
|
|
||||||
$counter = 0;
|
$counter = 0;
|
||||||
|
|
||||||
it('creates unique test case names', function (string $name, Plugin $plugin, bool $bool) use (&$counter) {
|
it('creates unique test case names', function (string $name, Plugin $plugin, bool $bool) use (&$counter): void {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
$counter++;
|
$counter++;
|
||||||
})->with([
|
})->with([
|
||||||
@@ -151,73 +144,69 @@ it('creates unique test case names', function (string $name, Plugin $plugin, boo
|
|||||||
['Name 1', new Plugin, true],
|
['Name 1', new Plugin, true],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
it('creates unique test case names - count', function () use (&$counter) {
|
it('creates unique test case names - count', function () use (&$counter): void {
|
||||||
expect($counter)->toBe(6);
|
expect($counter)->toBe(6);
|
||||||
});
|
});
|
||||||
|
|
||||||
$datasets_a = [[1], [2]];
|
$datasets_a = [[1], [2]];
|
||||||
$datasets_b = [[3], [4]];
|
$datasets_b = [[3], [4]];
|
||||||
|
|
||||||
test('lazy multiple datasets', function ($text_a, $text_b) use ($state, $datasets_a, $datasets_b) {
|
test('lazy multiple datasets', function ($text_a, $text_b) use ($state, $datasets_a, $datasets_b): void {
|
||||||
$state->text .= $text_a.$text_b;
|
$state->text .= $text_a.$text_b;
|
||||||
expect($datasets_a)->toContain([$text_a]);
|
expect($datasets_a)->toContain([$text_a])
|
||||||
expect($datasets_b)->toContain([$text_b]);
|
->and($datasets_b)->toContain([$text_b]);
|
||||||
})->with($datasets_a, $datasets_b);
|
})->with($datasets_a, $datasets_b);
|
||||||
|
|
||||||
test('lazy multiple datasets did the job right', function () use ($state) {
|
test('lazy multiple datasets did the job right', function () use ($state): void {
|
||||||
expect($state->text)->toBe('12121212121213142324');
|
expect($state->text)->toBe('12121212121213142324');
|
||||||
});
|
});
|
||||||
|
|
||||||
$state->text = '';
|
$state->text = '';
|
||||||
|
|
||||||
test('eager multiple datasets', function ($text_a, $text_b) use ($state, $datasets_a, $datasets_b) {
|
test('eager multiple datasets', function ($text_a, $text_b) use ($state, $datasets_a, $datasets_b): void {
|
||||||
$state->text .= $text_a.$text_b;
|
$state->text .= $text_a.$text_b;
|
||||||
expect($datasets_a)->toContain([$text_a]);
|
expect($datasets_a)->toContain([$text_a])
|
||||||
expect($datasets_b)->toContain([$text_b]);
|
->and($datasets_b)->toContain([$text_b]);
|
||||||
})->with(function () use ($datasets_a) {
|
})->with(fn (): array => $datasets_a)->with(fn (): array => $datasets_b);
|
||||||
return $datasets_a;
|
|
||||||
})->with(function () use ($datasets_b) {
|
|
||||||
return $datasets_b;
|
|
||||||
});
|
|
||||||
|
|
||||||
test('eager multiple datasets did the job right', function () use ($state) {
|
test('eager multiple datasets did the job right', function () use ($state): void {
|
||||||
expect($state->text)->toBe('1212121212121314232413142324');
|
expect($state->text)->toBe('1212121212121314232413142324');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('lazy registered multiple datasets', function ($text_a, $text_b) use ($state, $datasets) {
|
test('lazy registered multiple datasets', function ($text_a, $text_b) use ($state, $datasets): void {
|
||||||
$state->text .= $text_a.$text_b;
|
$state->text .= $text_a.$text_b;
|
||||||
expect($datasets)->toContain([$text_a]);
|
expect($datasets)->toContain([$text_a])
|
||||||
expect($datasets)->toContain([$text_b]);
|
->toContain([$text_b]);
|
||||||
})->with('numbers.array')->with('numbers.array');
|
})->with('numbers.array')->with('numbers.array');
|
||||||
|
|
||||||
test('lazy registered multiple datasets did the job right', function () use ($state) {
|
test('lazy registered multiple datasets did the job right', function () use ($state): void {
|
||||||
expect($state->text)->toBe('121212121212131423241314232411122122');
|
expect($state->text)->toBe('121212121212131423241314232411122122');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('eager registered multiple datasets', function ($text_a, $text_b) use ($state, $datasets) {
|
test('eager registered multiple datasets', function ($text_a, $text_b) use ($state, $datasets): void {
|
||||||
$state->text .= $text_a.$text_b;
|
$state->text .= $text_a.$text_b;
|
||||||
expect($datasets)->toContain([$text_a]);
|
expect($datasets)->toContain([$text_a])
|
||||||
expect($datasets)->toContain([$text_b]);
|
->toContain([$text_b]);
|
||||||
})->with('numbers.array')->with('numbers.closure');
|
})->with('numbers.array')->with('numbers.closure');
|
||||||
|
|
||||||
test('eager registered multiple datasets did the job right', function () use ($state) {
|
test('eager registered multiple datasets did the job right', function () use ($state): void {
|
||||||
expect($state->text)->toBe('12121212121213142324131423241112212211122122');
|
expect($state->text)->toBe('12121212121213142324131423241112212211122122');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('eager wrapped registered multiple datasets', function ($text_a, $text_b) use ($state, $datasets) {
|
test('eager wrapped registered multiple datasets', function ($text_a, $text_b) use ($state, $datasets): void {
|
||||||
$state->text .= $text_a.$text_b;
|
$state->text .= $text_a.$text_b;
|
||||||
expect($datasets)->toContain([$text_a]);
|
expect($datasets)->toContain([$text_a])
|
||||||
expect($datasets)->toContain([$text_b]);
|
->toContain([$text_b]);
|
||||||
})->with('numbers.closure.wrapped')->with('numbers.closure');
|
})->with('numbers.closure.wrapped')->with('numbers.closure');
|
||||||
|
|
||||||
test('eager wrapped registered multiple datasets did the job right', function () use ($state) {
|
test('eager wrapped registered multiple datasets did the job right', function () use ($state): void {
|
||||||
expect($state->text)->toBe('1212121212121314232413142324111221221112212211122122');
|
expect($state->text)->toBe('1212121212121314232413142324111221221112212211122122');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('named multiple datasets', function ($text_a, $text_b) use ($state, $datasets_a, $datasets_b) {
|
test('named multiple datasets', function ($text_a, $text_b) use ($state, $datasets_a, $datasets_b): void {
|
||||||
$state->text .= $text_a.$text_b;
|
$state->text .= $text_a.$text_b;
|
||||||
expect($datasets_a)->toContain([$text_a]);
|
expect($datasets_a)->toContain([$text_a])
|
||||||
expect($datasets_b)->toContain([$text_b]);
|
->and($datasets_b)->toContain([$text_b]);
|
||||||
})->with([
|
})->with([
|
||||||
'one' => [1],
|
'one' => [1],
|
||||||
'two' => [2],
|
'two' => [2],
|
||||||
@@ -226,18 +215,18 @@ test('named multiple datasets', function ($text_a, $text_b) use ($state, $datase
|
|||||||
'four' => [4],
|
'four' => [4],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('named multiple datasets did the job right', function () use ($state) {
|
test('named multiple datasets did the job right', function () use ($state): void {
|
||||||
expect($state->text)->toBe('121212121212131423241314232411122122111221221112212213142324');
|
expect($state->text)->toBe('121212121212131423241314232411122122111221221112212213142324');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('more than two datasets', function ($text_a, $text_b, $text_c) use ($state, $datasets_a, $datasets_b) {
|
test('more than two datasets', function ($text_a, $text_b, $text_c) use ($state, $datasets_a, $datasets_b): void {
|
||||||
$state->text .= $text_a.$text_b.$text_c;
|
$state->text .= $text_a.$text_b.$text_c;
|
||||||
expect($datasets_a)->toContain([$text_a]);
|
expect($datasets_a)->toContain([$text_a])
|
||||||
expect($datasets_b)->toContain([$text_b]);
|
->and($datasets_b)->toContain([$text_b])
|
||||||
expect([5, 6])->toContain($text_c);
|
->and([5, 6])->toContain($text_c);
|
||||||
})->with($datasets_a, $datasets_b)->with([5, 6]);
|
})->with($datasets_a, $datasets_b)->with([5, 6]);
|
||||||
|
|
||||||
test('more than two datasets did the job right', function () use ($state) {
|
test('more than two datasets did the job right', function () use ($state): void {
|
||||||
expect($state->text)->toBe('121212121212131423241314232411122122111221221112212213142324135136145146235236245246');
|
expect($state->text)->toBe('121212121212131423241314232411122122111221221112212213142324135136145146235236245246');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -250,125 +239,97 @@ test(
|
|||||||
function (int $text) use (
|
function (int $text) use (
|
||||||
$wrapped_generator_state,
|
$wrapped_generator_state,
|
||||||
$wrapped_generator_function_datasets
|
$wrapped_generator_function_datasets
|
||||||
) {
|
): void {
|
||||||
$wrapped_generator_state->text .= $text;
|
$wrapped_generator_state->text .= $text;
|
||||||
expect(in_array($text, $wrapped_generator_function_datasets))->toBe(true);
|
expect($text)->toBeIn($wrapped_generator_function_datasets);
|
||||||
}
|
}
|
||||||
)->with('numbers.generators.wrapped');
|
)->with('numbers.generators.wrapped');
|
||||||
|
|
||||||
test('eager registered wrapped datasets with Generator functions did the job right', function () use ($wrapped_generator_state) {
|
test('eager registered wrapped datasets with Generator functions did the job right', function () use ($wrapped_generator_state): void {
|
||||||
expect($wrapped_generator_state->text)->toBe('1234');
|
expect($wrapped_generator_state->text)->toBe('1234');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('eager registered wrapped datasets with Generator functions display description', function ($wrapped_generator_state_with_description) {
|
test('eager registered wrapped datasets with Generator functions display description', function ($wrapped_generator_state_with_description): void {
|
||||||
expect($wrapped_generator_state_with_description)->not->toBeEmpty();
|
expect($wrapped_generator_state_with_description)->not->toBeEmpty();
|
||||||
})->with(function () {
|
})->with(function () {
|
||||||
yield 'taylor' => 'taylor@laravel.com';
|
yield 'taylor' => 'taylor@laravel.com';
|
||||||
yield 'james' => 'james@laravel.com';
|
yield 'james' => 'james@laravel.com';
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can resolve a dataset after the test case is available', function ($result) {
|
it('can resolve a dataset after the test case is available', function ($result): void {
|
||||||
expect($result)->toBe('bar');
|
expect($result)->toBe('bar');
|
||||||
})->with([
|
})->with([
|
||||||
function () {
|
fn () => $this->foo,
|
||||||
return $this->foo;
|
|
||||||
},
|
|
||||||
[
|
[
|
||||||
function () {
|
fn () => $this->foo,
|
||||||
return $this->foo;
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
it('can resolve a dataset after the test case is available with multiple datasets', function (string $result, string $result2) {
|
it('can resolve a dataset after the test case is available with multiple datasets', function (string $result, string $result2): void {
|
||||||
expect($result)->toBe('bar');
|
expect($result)->toBe('bar');
|
||||||
})->with([
|
})->with([
|
||||||
function () {
|
fn () => $this->foo,
|
||||||
return $this->foo;
|
|
||||||
},
|
|
||||||
[
|
[
|
||||||
function () {
|
fn () => $this->foo,
|
||||||
return $this->foo;
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
], [
|
], [
|
||||||
function () {
|
fn () => $this->foo,
|
||||||
return $this->foo;
|
|
||||||
},
|
|
||||||
[
|
[
|
||||||
function () {
|
fn () => $this->foo,
|
||||||
return $this->foo;
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
it('can resolve a dataset after the test case is available with shared yield sets', function ($result) {
|
it('can resolve a dataset after the test case is available with shared yield sets', function ($result): void {
|
||||||
expect($result)->toBeInt()->toBeLessThan(3);
|
expect($result)->toBeInt()->toBeLessThan(3);
|
||||||
})->with('bound.closure');
|
})->with('bound.closure');
|
||||||
|
|
||||||
it('can resolve a dataset after the test case is available with shared array sets', function ($result) {
|
it('can resolve a dataset after the test case is available with shared array sets', function ($result): void {
|
||||||
expect($result)->toBeInt()->toBeLessThan(3);
|
expect($result)->toBeInt()->toBeLessThan(3);
|
||||||
})->with('bound.array');
|
})->with('bound.array');
|
||||||
|
|
||||||
it('resolves a potential bound dataset logically', function ($foo, $bar) {
|
it('resolves a potential bound dataset logically', function ($foo, $bar): void {
|
||||||
expect($foo)->toBe('foo');
|
expect($foo)->toBe('foo')
|
||||||
expect($bar())->toBe('bar');
|
->and($bar())->toBe('bar');
|
||||||
})->with([
|
})->with([
|
||||||
[
|
[
|
||||||
'foo',
|
'foo',
|
||||||
function () {
|
fn (): string => 'bar',
|
||||||
return 'bar';
|
|
||||||
},
|
|
||||||
], // This should be passed as a closure because we've passed multiple arguments
|
], // This should be passed as a closure because we've passed multiple arguments
|
||||||
]);
|
]);
|
||||||
|
|
||||||
it('resolves a potential bound dataset logically even when the closure comes first', function ($foo, $bar) {
|
it('resolves a potential bound dataset logically even when the closure comes first', function ($foo, $bar): void {
|
||||||
expect($foo())->toBe('foo');
|
expect($foo())->toBe('foo')
|
||||||
expect($bar)->toBe('bar');
|
->and($bar)->toBe('bar');
|
||||||
})->with([
|
})->with([
|
||||||
[
|
[
|
||||||
function () {
|
fn (): string => 'foo', 'bar',
|
||||||
return 'foo';
|
|
||||||
}, 'bar',
|
|
||||||
], // This should be passed as a closure because we've passed multiple arguments
|
], // This should be passed as a closure because we've passed multiple arguments
|
||||||
]);
|
]);
|
||||||
|
|
||||||
it('will not resolve a closure if it is type hinted as a closure', function (Closure $data) {
|
it('will not resolve a closure if it is type hinted as a closure', function (Closure $data): void {
|
||||||
expect($data())->toBeString();
|
expect($data())->toBeString();
|
||||||
})->with([
|
})->with([
|
||||||
function () {
|
fn (): string => 'foo',
|
||||||
return 'foo';
|
fn (): string => 'bar',
|
||||||
},
|
|
||||||
function () {
|
|
||||||
return 'bar';
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
it('will not resolve a closure if it is type hinted as a callable', function (callable $data) {
|
it('will not resolve a closure if it is type hinted as a callable', function (callable $data): void {
|
||||||
expect($data())->toBeString();
|
expect($data())->toBeString();
|
||||||
})->with([
|
})->with([
|
||||||
function () {
|
fn (): string => 'foo',
|
||||||
return 'foo';
|
fn (): string => 'bar',
|
||||||
},
|
|
||||||
function () {
|
|
||||||
return 'bar';
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
it('can correctly resolve a bound dataset that returns an array', function (array $data) {
|
it('can correctly resolve a bound dataset that returns an array', function (array $data): void {
|
||||||
expect($data)->toBe(['foo', 'bar', 'baz']);
|
expect($data)->toBe(['foo', 'bar', 'baz']);
|
||||||
})->with([
|
})->with([
|
||||||
function () {
|
fn (): array => ['foo', 'bar', 'baz'],
|
||||||
return ['foo', 'bar', 'baz'];
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
it('can correctly resolve a bound dataset that returns an array but wants to be spread', function (string $foo, string $bar, string $baz) {
|
it('can correctly resolve a bound dataset that returns an array but wants to be spread', function (string $foo, string $bar, string $baz): void {
|
||||||
expect([$foo, $bar, $baz])->toBe(['foo', 'bar', 'baz']);
|
expect([$foo, $bar, $baz])->toBe(['foo', 'bar', 'baz']);
|
||||||
})->with([
|
})->with([
|
||||||
function () {
|
fn (): array => ['foo', 'bar', 'baz'],
|
||||||
return ['foo', 'bar', 'baz'];
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
todo('forbids to define tests in Datasets dirs and Datasets.php files');
|
todo('forbids to define tests in Datasets dirs and Datasets.php files');
|
||||||
@@ -393,57 +354,57 @@ it('may be used with high order even when bound')
|
|||||||
->expect(fn (string $greeting) => $greeting)
|
->expect(fn (string $greeting) => $greeting)
|
||||||
->throws(InvalidArgumentException::class);
|
->throws(InvalidArgumentException::class);
|
||||||
|
|
||||||
describe('with on nested describe', function () {
|
describe('with on nested describe', function (): void {
|
||||||
describe('nested', function () {
|
describe('nested', function (): void {
|
||||||
test('before inner describe block', function (...$args) {
|
test('before inner describe block', function (...$args): void {
|
||||||
expect($args)->toBe([1]);
|
expect($args)->toBe([1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('describe', function () {
|
describe('describe', function (): void {
|
||||||
it('should include the with value from all parent describe blocks', function (...$args) {
|
it('should include the with value from all parent describe blocks', function (...$args): void {
|
||||||
expect($args)->toBe([1, 2]);
|
expect($args)->toBe([1, 2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should include the with value from all parent describe blocks and the test', function (...$args) {
|
test('should include the with value from all parent describe blocks and the test', function (...$args): void {
|
||||||
expect($args)->toBe([1, 2, 3]);
|
expect($args)->toBe([1, 2, 3]);
|
||||||
})->with([3]);
|
})->with([3]);
|
||||||
})->with([2]);
|
})->with([2]);
|
||||||
|
|
||||||
test('after inner describe block', function (...$args) {
|
test('after inner describe block', function (...$args): void {
|
||||||
expect($args)->toBe([1]);
|
expect($args)->toBe([1]);
|
||||||
});
|
});
|
||||||
})->with([1]);
|
})->with([1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('matching describe block names', function () {
|
describe('matching describe block names', function (): void {
|
||||||
describe('outer', function () {
|
describe('outer', function (): void {
|
||||||
test('before inner describe block', function (...$args) {
|
test('before inner describe block', function (...$args): void {
|
||||||
expect($args)->toBe([1]);
|
expect($args)->toBe([1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inner', function () {
|
describe('inner', function (): void {
|
||||||
it('should include the with value from all parent describe blocks', function (...$args) {
|
it('should include the with value from all parent describe blocks', function (...$args): void {
|
||||||
expect($args)->toBe([1, 2]);
|
expect($args)->toBe([1, 2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should include the with value from all parent describe blocks and the test', function (...$args) {
|
test('should include the with value from all parent describe blocks and the test', function (...$args): void {
|
||||||
expect($args)->toBe([1, 2, 3]);
|
expect($args)->toBe([1, 2, 3]);
|
||||||
})->with([3]);
|
})->with([3]);
|
||||||
})->with([2]);
|
})->with([2]);
|
||||||
|
|
||||||
describe('inner', function () {
|
describe('inner', function (): void {
|
||||||
it('should not include the value from the other describe block with the same name', function (...$args) {
|
it('should not include the value from the other describe block with the same name', function (...$args): void {
|
||||||
expect($args)->toBe([1]);
|
expect($args)->toBe([1]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('after inner describe block', function (...$args) {
|
test('after inner describe block', function (...$args): void {
|
||||||
expect($args)->toBe([1]);
|
expect($args)->toBe([1]);
|
||||||
});
|
});
|
||||||
})->with([1]);
|
})->with([1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('after describe block', function (...$args) {
|
test('after describe block', function (...$args): void {
|
||||||
expect($args)->toBe([5]);
|
expect($args)->toBe([5]);
|
||||||
})->with([5]);
|
})->with([5]);
|
||||||
|
|
||||||
@@ -454,64 +415,58 @@ it('may be used with high order after describe block')
|
|||||||
|
|
||||||
dataset('after-describe', ['after']);
|
dataset('after-describe', ['after']);
|
||||||
|
|
||||||
test('after describe block with named dataset', function (...$args) {
|
test('after describe block with named dataset', function (...$args): void {
|
||||||
expect($args)->toBe(['after']);
|
expect($args)->toBe(['after']);
|
||||||
})->with('after-describe');
|
})->with('after-describe');
|
||||||
|
|
||||||
test('named parameters match by parameter name', function (string $email, string $name) {
|
test('named parameters match by parameter name', function (string $email, string $name): void {
|
||||||
expect($name)->toBe('Taylor');
|
expect($name)->toBe('Taylor')
|
||||||
expect($email)->toBe('taylor@laravel.com');
|
->and($email)->toBe('taylor@laravel.com');
|
||||||
})->with([
|
})->with([
|
||||||
['name' => 'Taylor', 'email' => 'taylor@laravel.com'],
|
['name' => 'Taylor', 'email' => 'taylor@laravel.com'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('named parameters work with multiple dataset items', function (string $email, string $name) {
|
test('named parameters work with multiple dataset items', function (string $email, string $name): void {
|
||||||
expect($name)->toBeString();
|
expect($name)->toBeString()
|
||||||
expect($email)->toContain('@');
|
->and($email)->toContain('@');
|
||||||
})->with([
|
})->with([
|
||||||
['name' => 'Taylor', 'email' => 'taylor@laravel.com'],
|
['name' => 'Taylor', 'email' => 'taylor@laravel.com'],
|
||||||
['name' => 'James', 'email' => 'james@laravel.com'],
|
['name' => 'James', 'email' => 'james@laravel.com'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('named parameters work in different order than closure params', function (string $third, string $first, string $second) {
|
test('named parameters work in different order than closure params', function (string $third, string $first, string $second): void {
|
||||||
expect($first)->toBe('a');
|
expect($first)->toBe('a')
|
||||||
expect($second)->toBe('b');
|
->and($second)->toBe('b')
|
||||||
expect($third)->toBe('c');
|
->and($third)->toBe('c');
|
||||||
})->with([
|
})->with([
|
||||||
['first' => 'a', 'second' => 'b', 'third' => 'c'],
|
['first' => 'a', 'second' => 'b', 'third' => 'c'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('named parameters work with named dataset keys', function (string $email, string $name) {
|
test('named parameters work with named dataset keys', function (string $email, string $name): void {
|
||||||
expect($name)->toBeString();
|
expect($name)->toBeString()
|
||||||
expect($email)->toContain('@');
|
->and($email)->toContain('@');
|
||||||
})->with([
|
})->with([
|
||||||
'taylor' => ['name' => 'Taylor', 'email' => 'taylor@laravel.com'],
|
'taylor' => ['name' => 'Taylor', 'email' => 'taylor@laravel.com'],
|
||||||
'james' => ['name' => 'James', 'email' => 'james@laravel.com'],
|
'james' => ['name' => 'James', 'email' => 'james@laravel.com'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('named parameters work with closures that should be resolved', function (string $email, string $name) {
|
test('named parameters work with closures that should be resolved', function (string $email, string $name): void {
|
||||||
expect($name)->toBe('bar');
|
expect($name)->toBe('bar')
|
||||||
expect($email)->toBe('bar@example.com');
|
->and($email)->toBe('bar@example.com');
|
||||||
})->with([
|
})->with([
|
||||||
[
|
[
|
||||||
'name' => function () {
|
'name' => fn () => $this->foo,
|
||||||
return $this->foo;
|
'email' => fn (): string => $this->foo.'@example.com',
|
||||||
},
|
|
||||||
'email' => function () {
|
|
||||||
return $this->foo.'@example.com';
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('named parameters work with closure type hints', function (Closure $callback, string $name) {
|
test('named parameters work with closure type hints', function (Closure $callback, string $name): void {
|
||||||
expect($name)->toBe('Taylor');
|
expect($name)->toBe('Taylor')
|
||||||
expect($callback())->toBe('resolved');
|
->and($callback())->toBe('resolved');
|
||||||
})->with([
|
})->with([
|
||||||
[
|
[
|
||||||
'name' => 'Taylor',
|
'name' => 'Taylor',
|
||||||
'callback' => function () {
|
'callback' => fn (): string => 'resolved',
|
||||||
return 'resolved';
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -520,23 +475,21 @@ dataset('named-params-dataset', [
|
|||||||
['name' => 'James', 'email' => 'james@laravel.com'],
|
['name' => 'James', 'email' => 'james@laravel.com'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('named parameters work with registered datasets', function (string $email, string $name) {
|
test('named parameters work with registered datasets', function (string $email, string $name): void {
|
||||||
expect($name)->toBeString();
|
expect($name)->toBeString()
|
||||||
expect($email)->toContain('@');
|
->and($email)->toContain('@');
|
||||||
})->with('named-params-dataset');
|
})->with('named-params-dataset');
|
||||||
|
|
||||||
test('named parameters work with bound closure returning associative array', function (string $email, string $name) {
|
test('named parameters work with bound closure returning associative array', function (string $email, string $name): void {
|
||||||
expect($name)->toBe('bar');
|
expect($name)->toBe('bar')
|
||||||
expect($email)->toBe('test@example.com');
|
->and($email)->toBe('test@example.com');
|
||||||
})->with([
|
})->with([
|
||||||
function () {
|
fn (): array => ['name' => $this->foo, 'email' => 'test@example.com'],
|
||||||
return ['name' => $this->foo, 'email' => 'test@example.com'];
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
test('dataset items can mix named and sequential styles', function (string $name, string $email) {
|
test('dataset items can mix named and sequential styles', function (string $name, string $email): void {
|
||||||
expect($name)->toBeString();
|
expect($name)->toBeString()
|
||||||
expect($email)->toContain('@');
|
->and($email)->toContain('@');
|
||||||
})->with([
|
})->with([
|
||||||
['name' => 'Taylor', 'email' => 'taylor@laravel.com'],
|
['name' => 'Taylor', 'email' => 'taylor@laravel.com'],
|
||||||
['James', 'james@laravel.com'],
|
['James', 'james@laravel.com'],
|
||||||
|
|||||||
+17
-17
@@ -2,34 +2,34 @@
|
|||||||
|
|
||||||
$runCounter = 0;
|
$runCounter = 0;
|
||||||
|
|
||||||
test('first', function () use (&$runCounter) {
|
test('first', function () use (&$runCounter): string {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
$runCounter++;
|
$runCounter++;
|
||||||
|
|
||||||
return 'first';
|
return 'first';
|
||||||
});
|
});
|
||||||
|
|
||||||
test('second', function () use (&$runCounter) {
|
test('second', function () use (&$runCounter): string {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
$runCounter++;
|
$runCounter++;
|
||||||
|
|
||||||
return 'second';
|
return 'second';
|
||||||
});
|
});
|
||||||
|
|
||||||
test('depends', function () {
|
test('depends', function (): void {
|
||||||
expect(func_get_args())->toBe(['first', 'second']);
|
expect(func_get_args())->toBe(['first', 'second']);
|
||||||
})->depends('first', 'second');
|
})->depends('first', 'second');
|
||||||
|
|
||||||
test('depends with ...params', function (string ...$params) {
|
test('depends with ...params', function (string ...$params): void {
|
||||||
expect(func_get_args())->toBe($params);
|
expect(func_get_args())->toBe($params);
|
||||||
})->depends('first', 'second');
|
})->depends('first', 'second');
|
||||||
|
|
||||||
test('depends with defined arguments', function (string $first, string $second) {
|
test('depends with defined arguments', function (string $first, string $second): void {
|
||||||
expect($first)->toBe('first');
|
expect($first)->toBe('first')
|
||||||
expect($second)->toBe('second');
|
->and($second)->toBe('second');
|
||||||
})->depends('first', 'second');
|
})->depends('first', 'second');
|
||||||
|
|
||||||
test('depends run test only once', function () use (&$runCounter) {
|
test('depends run test only once', function () use (&$runCounter): void {
|
||||||
expect($runCounter)->toBe(2);
|
expect($runCounter)->toBe(2);
|
||||||
})->depends('first', 'second');
|
})->depends('first', 'second');
|
||||||
|
|
||||||
@@ -37,42 +37,42 @@ test('depends run test only once', function () use (&$runCounter) {
|
|||||||
it('asserts true is true')->assertTrue(true);
|
it('asserts true is true')->assertTrue(true);
|
||||||
test('depends works with the correct test name')->assertTrue(true)->depends('it asserts true is true');
|
test('depends works with the correct test name')->assertTrue(true)->depends('it asserts true is true');
|
||||||
|
|
||||||
describe('describe block', function () {
|
describe('describe block', function (): void {
|
||||||
$runCounter = 0;
|
$runCounter = 0;
|
||||||
|
|
||||||
test('first in describe', function () use (&$runCounter) {
|
test('first in describe', function () use (&$runCounter): void {
|
||||||
$runCounter++;
|
$runCounter++;
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('second in describe', function () use (&$runCounter) {
|
test('second in describe', function () use (&$runCounter): void {
|
||||||
expect($runCounter)->toBe(1);
|
expect($runCounter)->toBe(1);
|
||||||
$runCounter++;
|
$runCounter++;
|
||||||
})->depends('first in describe');
|
})->depends('first in describe');
|
||||||
|
|
||||||
test('third in describe', function () use (&$runCounter) {
|
test('third in describe', function () use (&$runCounter): void {
|
||||||
expect($runCounter)->toBe(2);
|
expect($runCounter)->toBe(2);
|
||||||
})->depends('second in describe');
|
})->depends('second in describe');
|
||||||
|
|
||||||
describe('nested describe', function () {
|
describe('nested describe', function (): void {
|
||||||
$runCounter = 0;
|
$runCounter = 0;
|
||||||
|
|
||||||
test('first in nested describe', function () use (&$runCounter) {
|
test('first in nested describe', function () use (&$runCounter): void {
|
||||||
$runCounter++;
|
$runCounter++;
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('second in nested describe', function () use (&$runCounter) {
|
test('second in nested describe', function () use (&$runCounter): void {
|
||||||
expect($runCounter)->toBe(1);
|
expect($runCounter)->toBe(1);
|
||||||
$runCounter++;
|
$runCounter++;
|
||||||
})->depends('first in nested describe');
|
})->depends('first in nested describe');
|
||||||
|
|
||||||
test('third in nested describe', function () use (&$runCounter) {
|
test('third in nested describe', function () use (&$runCounter): void {
|
||||||
expect($runCounter)->toBe(2);
|
expect($runCounter)->toBe(2);
|
||||||
})->depends('second in nested describe');
|
})->depends('second in nested describe');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('depends on test after describe block', function () use (&$runCounter) {
|
test('depends on test after describe block', function () use (&$runCounter): void {
|
||||||
expect($runCounter)->toBe(2);
|
expect($runCounter)->toBe(2);
|
||||||
})->depends('first', 'second');
|
})->depends('first', 'second');
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use PHPUnit\Framework\TestCase;
|
|||||||
|
|
||||||
class InheritanceTest extends TestCase
|
class InheritanceTest extends TestCase
|
||||||
{
|
{
|
||||||
public function foo()
|
public function foo(): string
|
||||||
{
|
{
|
||||||
return 'bar';
|
return 'bar';
|
||||||
}
|
}
|
||||||
@@ -12,11 +12,11 @@ class InheritanceTest extends TestCase
|
|||||||
|
|
||||||
pest()->extend(InheritanceTest::class);
|
pest()->extend(InheritanceTest::class);
|
||||||
|
|
||||||
it('is a test', function () {
|
it('is a test', function (): void {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses correct parent class', function () {
|
it('uses correct parent class', function (): void {
|
||||||
expect(get_parent_class($this))->toEqual(InheritanceTest::class);
|
expect(get_parent_class($this))->toEqual(InheritanceTest::class)
|
||||||
expect($this->foo())->toEqual('bar');
|
->and($this->foo())->toEqual('bar');
|
||||||
})->depends('it is a test');
|
})->depends('it is a test');
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
test('deprecated', function () {
|
declare(strict_types=1);
|
||||||
str_contains(null, null);
|
|
||||||
|
test('deprecated', function (): void {
|
||||||
|
str_contains('', '');
|
||||||
|
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('user deprecated', function () {
|
test('user deprecated', function (): void {
|
||||||
trigger_error('Since foo 1.0: This is a deprecation description', \E_USER_DEPRECATED);
|
trigger_error('Since foo 1.0: This is a deprecation description', \E_USER_DEPRECATED);
|
||||||
|
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
|
|||||||
+27
-27
@@ -2,58 +2,58 @@
|
|||||||
|
|
||||||
beforeEach(fn () => $this->count = 1);
|
beforeEach(fn () => $this->count = 1);
|
||||||
|
|
||||||
test('before each', function () {
|
test('before each', function (): void {
|
||||||
expect($this->count)->toBe(1);
|
expect($this->count)->toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('hooks', function () {
|
describe('hooks', function (): void {
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->count++;
|
$this->count++;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('value', function () {
|
test('value', function (): void {
|
||||||
expect($this->count)->toBe(2);
|
expect($this->count)->toBe(2);
|
||||||
$this->count++;
|
$this->count++;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function (): void {
|
||||||
expect($this->count)->toBe(3);
|
expect($this->count)->toBe(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('hooks in different orders', function () {
|
describe('hooks in different orders', function (): void {
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->count++;
|
$this->count++;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('value', function () {
|
test('value', function (): void {
|
||||||
expect($this->count)->toBe(3);
|
expect($this->count)->toBe(3);
|
||||||
$this->count++;
|
$this->count++;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function (): void {
|
||||||
expect($this->count)->toBe(4);
|
expect($this->count)->toBe(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->count++;
|
$this->count++;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('todo')->todo()->shouldNotRun();
|
test('todo')->todo()->shouldNotRun();
|
||||||
|
|
||||||
test('previous describable before each does not get applied here', function () {
|
test('previous describable before each does not get applied here', function (): void {
|
||||||
expect($this->count)->toBe(1);
|
expect($this->count)->toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('todo on hook', function () {
|
describe('todo on hook', function (): void {
|
||||||
beforeEach()->todo();
|
beforeEach()->todo();
|
||||||
|
|
||||||
test('should not fail')->shouldNotRun();
|
test('should not fail')->shouldNotRun();
|
||||||
test('should run')->expect(true)->toBeTrue();
|
test('should run')->expect(true)->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('todo on describe', function () {
|
describe('todo on describe', function (): void {
|
||||||
test('should not fail')->shouldNotRun();
|
test('should not fail')->shouldNotRun();
|
||||||
|
|
||||||
test('should run')->expect(true)->toBeTrue();
|
test('should run')->expect(true)->toBeTrue();
|
||||||
@@ -63,48 +63,48 @@ test('should run')->expect(true)->toBeTrue();
|
|||||||
|
|
||||||
test('with', fn ($foo) => expect($foo)->toBe(1))->with([1]);
|
test('with', fn ($foo) => expect($foo)->toBe(1))->with([1]);
|
||||||
|
|
||||||
describe('with on hook', function () {
|
describe('with on hook', function (): void {
|
||||||
beforeEach()->with([2]);
|
beforeEach()->with([2]);
|
||||||
|
|
||||||
test('value', function ($foo) {
|
test('value', function ($foo): void {
|
||||||
expect($foo)->toBe(2);
|
expect($foo)->toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with on describe', function () {
|
describe('with on describe', function (): void {
|
||||||
test('value', function ($foo) {
|
test('value', function ($foo): void {
|
||||||
expect($foo)->toBe(3);
|
expect($foo)->toBe(3);
|
||||||
});
|
});
|
||||||
})->with([3]);
|
})->with([3]);
|
||||||
|
|
||||||
describe('depends on describe', function () {
|
describe('depends on describe', function (): void {
|
||||||
test('foo', function () {
|
test('foo', function (): void {
|
||||||
expect('foo')->toBe('foo');
|
expect('foo')->toBe('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('bar', function () {
|
test('bar', function (): void {
|
||||||
expect('bar')->toBe('bar');
|
expect('bar')->toBe('bar');
|
||||||
})->depends('foo');
|
})->depends('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('depends on describe using with', function () {
|
describe('depends on describe using with', function (): void {
|
||||||
test('foo', function ($foo) {
|
test('foo', function ($foo): void {
|
||||||
expect($foo)->toBe(3);
|
expect($foo)->toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('bar', function ($foo) {
|
test('bar', function ($foo): void {
|
||||||
expect($foo + $foo)->toBe(6);
|
expect($foo + $foo)->toBe(6);
|
||||||
})->depends('foo');
|
})->depends('foo');
|
||||||
})->with([3]);
|
})->with([3]);
|
||||||
|
|
||||||
describe('with test after describe', function () {
|
describe('with test after describe', function (): void {
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->count++;
|
$this->count++;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('foo', function () {});
|
describe('foo', function (): void {});
|
||||||
|
|
||||||
it('should run the before each', function () {
|
it('should run the before each', function (): void {
|
||||||
expect($this->count)->toBe(2);
|
expect($this->count)->toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ get('foo'); // not incomplete because closure is created...
|
|||||||
get('foo')->get('bar')->expect(true)->toBeTrue();
|
get('foo')->get('bar')->expect(true)->toBeTrue();
|
||||||
get('foo')->expect(true)->toBeTrue();
|
get('foo')->expect(true)->toBeTrue();
|
||||||
|
|
||||||
describe('a "describe" group of tests', function () {
|
describe('a "describe" group of tests', function (): void {
|
||||||
get('foo'); // not incomplete because closure is created...
|
get('foo'); // not incomplete because closure is created...
|
||||||
get('foo')->get('bar')->expect(true)->toBeTrue();
|
get('foo')->get('bar')->expect(true)->toBeTrue();
|
||||||
get('foo')->expect(true)->toBeTrue();
|
get('foo')->expect(true)->toBeTrue();
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
it('may have an associated assignee', function () {
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
it('may have an associated assignee', function (): void {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
})->done(assignee: 'nunomaduro');
|
})->done(assignee: 'nunomaduro');
|
||||||
|
|
||||||
it('may have an associated issue', function () {
|
it('may have an associated issue', function (): void {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
})->done(issue: 1);
|
})->done(issue: 1);
|
||||||
|
|
||||||
it('may have an associated PR', function () {
|
it('may have an associated PR', function (): void {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
})->done(pr: 1);
|
})->done(pr: 1);
|
||||||
|
|
||||||
it('may have an associated note', function () {
|
it('may have an associated note', function (): void {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
})->done(note: 'a note');
|
})->done(note: 'a note');
|
||||||
|
|||||||
@@ -1,95 +1,89 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
it('gives access the the underlying expectException', function () {
|
it('gives access the the underlying expectException', function (): void {
|
||||||
$this->expectException(InvalidArgumentException::class);
|
expect(fn () => throw new InvalidArgumentException)->toThrow(InvalidArgumentException::class);
|
||||||
|
|
||||||
throw new InvalidArgumentException;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('catch exceptions', function () {
|
it('catch exceptions', function (): void {
|
||||||
throw new Exception('Something bad happened');
|
throw new Exception('Something bad happened');
|
||||||
})->throws(Exception::class);
|
})->throws(Exception::class);
|
||||||
|
|
||||||
it('catch exceptions and messages', function () {
|
it('catch exceptions and messages', function (): void {
|
||||||
throw new Exception('Something bad happened');
|
throw new Exception('Something bad happened');
|
||||||
})->throws(Exception::class, 'Something bad happened');
|
})->throws(Exception::class, 'Something bad happened');
|
||||||
|
|
||||||
it('catch exceptions, messages and code', function () {
|
it('catch exceptions, messages and code', function (): void {
|
||||||
throw new Exception('Something bad happened', 1);
|
throw new Exception('Something bad happened', 1);
|
||||||
})->throws(Exception::class, 'Something bad happened', 1);
|
})->throws(Exception::class, 'Something bad happened', 1);
|
||||||
|
|
||||||
it('can just define the message', function () {
|
it('can just define the message', function (): void {
|
||||||
throw new Exception('Something bad happened');
|
throw new Exception('Something bad happened');
|
||||||
})->throws('Something bad happened');
|
})->throws('Something bad happened');
|
||||||
|
|
||||||
it('can just define the code', function () {
|
it('can just define the code', function (): void {
|
||||||
throw new Exception('Something bad happened', 1);
|
throw new Exception('Something bad happened', 1);
|
||||||
})->throws(1);
|
})->throws(1);
|
||||||
|
|
||||||
it('not catch exceptions if given condition is false', function () {
|
it('not catch exceptions if given condition is false', function (): void {
|
||||||
$this->assertTrue(true);
|
expect(true)->toBeTrue();
|
||||||
})->throwsIf(false, Exception::class);
|
})->throwsIf(false, Exception::class);
|
||||||
|
|
||||||
it('catch exceptions if given condition is true', function () {
|
it('catch exceptions if given condition is true', function (): void {
|
||||||
throw new Exception('Something bad happened');
|
throw new Exception('Something bad happened');
|
||||||
})->throwsIf(function () {
|
})->throwsIf(fn (): true => true, Exception::class);
|
||||||
return true;
|
|
||||||
}, Exception::class);
|
|
||||||
|
|
||||||
it('catch exceptions and messages if given condition is true', function () {
|
it('catch exceptions and messages if given condition is true', function (): void {
|
||||||
throw new Exception('Something bad happened');
|
throw new Exception('Something bad happened');
|
||||||
})->throwsIf(true, Exception::class, 'Something bad happened');
|
})->throwsIf(true, Exception::class, 'Something bad happened');
|
||||||
|
|
||||||
it('catch exceptions, messages and code if given condition is true', function () {
|
it('catch exceptions, messages and code if given condition is true', function (): void {
|
||||||
throw new Exception('Something bad happened', 1);
|
throw new Exception('Something bad happened', 1);
|
||||||
})->throwsIf(true, Exception::class, 'Something bad happened', 1);
|
})->throwsIf(true, Exception::class, 'Something bad happened', 1);
|
||||||
|
|
||||||
it('can just define the message if given condition is true', function () {
|
it('can just define the message if given condition is true', function (): void {
|
||||||
throw new Exception('Something bad happened');
|
throw new Exception('Something bad happened');
|
||||||
})->throwsIf(true, 'Something bad happened');
|
})->throwsIf(true, 'Something bad happened');
|
||||||
|
|
||||||
it('can just define the code if given condition is true', function () {
|
it('can just define the code if given condition is true', function (): void {
|
||||||
throw new Exception('Something bad happened', 1);
|
throw new Exception('Something bad happened', 1);
|
||||||
})->throwsIf(true, 1);
|
})->throwsIf(true, 1);
|
||||||
|
|
||||||
it('can just define the message if given condition is 1', function () {
|
it('can just define the message if given condition is 1', function (): void {
|
||||||
throw new Exception('Something bad happened');
|
throw new Exception('Something bad happened');
|
||||||
})->throwsIf(1, 'Something bad happened');
|
})->throwsIf(1, 'Something bad happened');
|
||||||
|
|
||||||
it('can just define the code if given condition is 1', function () {
|
it('can just define the code if given condition is 1', function (): void {
|
||||||
throw new Exception('Something bad happened', 1);
|
throw new Exception('Something bad happened', 1);
|
||||||
})->throwsIf(1, 1);
|
})->throwsIf(1, 1);
|
||||||
|
|
||||||
it('not catch exceptions if given condition is true', function () {
|
it('not catch exceptions if given condition is true', function (): void {
|
||||||
$this->assertTrue(true);
|
expect(true)->toBeTrue();
|
||||||
})->throwsUnless(true, Exception::class);
|
})->throwsUnless(true, Exception::class);
|
||||||
|
|
||||||
it('catch exceptions if given condition is false', function () {
|
it('catch exceptions if given condition is false', function (): void {
|
||||||
throw new Exception('Something bad happened');
|
throw new Exception('Something bad happened');
|
||||||
})->throwsUnless(function () {
|
})->throwsUnless(fn (): false => false, Exception::class);
|
||||||
return false;
|
|
||||||
}, Exception::class);
|
|
||||||
|
|
||||||
it('catch exceptions and messages if given condition is false', function () {
|
it('catch exceptions and messages if given condition is false', function (): void {
|
||||||
throw new Exception('Something bad happened');
|
throw new Exception('Something bad happened');
|
||||||
})->throwsUnless(false, Exception::class, 'Something bad happened');
|
})->throwsUnless(false, Exception::class, 'Something bad happened');
|
||||||
|
|
||||||
it('catch exceptions, messages and code if given condition is false', function () {
|
it('catch exceptions, messages and code if given condition is false', function (): void {
|
||||||
throw new Exception('Something bad happened', 1);
|
throw new Exception('Something bad happened', 1);
|
||||||
})->throwsUnless(false, Exception::class, 'Something bad happened', 1);
|
})->throwsUnless(false, Exception::class, 'Something bad happened', 1);
|
||||||
|
|
||||||
it('can just define the message if given condition is false', function () {
|
it('can just define the message if given condition is false', function (): void {
|
||||||
throw new Exception('Something bad happened');
|
throw new Exception('Something bad happened');
|
||||||
})->throwsUnless(false, 'Something bad happened');
|
})->throwsUnless(false, 'Something bad happened');
|
||||||
|
|
||||||
it('can just define the code if given condition is false', function () {
|
it('can just define the code if given condition is false', function (): void {
|
||||||
throw new Exception('Something bad happened', 1);
|
throw new Exception('Something bad happened', 1);
|
||||||
})->throwsUnless(false, 1);
|
})->throwsUnless(false, 1);
|
||||||
|
|
||||||
it('can just define the message if given condition is 0', function () {
|
it('can just define the message if given condition is 0', function (): void {
|
||||||
throw new Exception('Something bad happened');
|
throw new Exception('Something bad happened');
|
||||||
})->throwsUnless(0, 'Something bad happened');
|
})->throwsUnless(0, 'Something bad happened');
|
||||||
|
|
||||||
it('can just define the code if given condition is 0', function () {
|
it('can just define the code if given condition is 0', function (): void {
|
||||||
throw new Exception('Something bad happened', 1);
|
throw new Exception('Something bad happened', 1);
|
||||||
})->throwsUnless(0, 1);
|
})->throwsUnless(0, 1);
|
||||||
|
|||||||
@@ -1,73 +1,72 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
it('can access methods', function () {
|
it('can access methods', function (): void {
|
||||||
expect(new HasMethods)
|
expect(new HasMethods)
|
||||||
->name()->toBeString()->toEqual('Has Methods');
|
->name()->toBeString()->toEqual('Has Methods');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can access multiple methods', function () {
|
it('can access multiple methods', function (): void {
|
||||||
expect(new HasMethods)
|
expect(new HasMethods)
|
||||||
->name()->toBeString()->toEqual('Has Methods')
|
->name()->toBeString()->toEqual('Has Methods')
|
||||||
->quantity()->toBeInt()->toEqual(20);
|
->quantity()->toBeInt()->toEqual(20);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with not', function () {
|
it('works with not', function (): void {
|
||||||
expect(new HasMethods)
|
expect(new HasMethods)
|
||||||
->name()->not->toEqual('world')->toEqual('Has Methods')
|
->name()->not->toEqual('world')->toEqual('Has Methods')
|
||||||
->quantity()->toEqual(20)->not()->toEqual('bar')->not->toBeNull;
|
->quantity()->toEqual(20)->not()->toEqual('bar')->not->toBeNull;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can accept arguments', function () {
|
it('can accept arguments', function (): void {
|
||||||
expect(new HasMethods)
|
expect(new HasMethods)
|
||||||
->multiply(5, 4)->toBeInt->toEqual(20);
|
->multiply(5, 4)->toBeInt->toEqual(20);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with each', function () {
|
it('works with each', function (): void {
|
||||||
expect(new HasMethods)
|
expect(new HasMethods)
|
||||||
->attributes()->toBeArray->each->not()->toBeNull
|
->attributes()->toBeArray->each->not()->toBeNull
|
||||||
->attributes()->each(function ($attribute) {
|
->attributes()->each(function ($attribute): void {
|
||||||
$attribute->not->toBeNull();
|
$attribute->not->toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works inside of each', function () {
|
it('works inside of each', function (): void {
|
||||||
expect(new HasMethods)
|
expect(new HasMethods)
|
||||||
->books()->each(function ($book) {
|
->books()->each(function ($book): void {
|
||||||
$book->title->not->toBeNull->cost->toBeGreaterThan(19);
|
$book->title->not->toBeNull->cost->toBeGreaterThan(19);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with sequence', function () {
|
it('works with sequence', function (): void {
|
||||||
expect(new HasMethods)
|
expect(new HasMethods)
|
||||||
->books()->sequence(
|
->books()->sequence(
|
||||||
function ($book) {
|
function ($book): void {
|
||||||
$book->title->toEqual('Foo')->cost->toEqual(20);
|
$book->title->toEqual('Foo')->cost->toEqual(20);
|
||||||
},
|
},
|
||||||
function ($book) {
|
function ($book): void {
|
||||||
$book->title->toEqual('Bar')->cost->toEqual(30);
|
$book->title->toEqual('Bar')->cost->toEqual(30);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can compose complex expectations', function () {
|
it('can compose complex expectations', function (): void {
|
||||||
expect(new HasMethods)
|
expect(new HasMethods)
|
||||||
->toBeObject()
|
|
||||||
->name()->toEqual('Has Methods')->not()->toEqual('bar')
|
->name()->toEqual('Has Methods')->not()->toEqual('bar')
|
||||||
->quantity()->not->toEqual('world')->toEqual(20)->toBeInt
|
->quantity()->not->toEqual('world')->toEqual(20)->toBeInt
|
||||||
->multiply(3, 4)->not->toBeString->toEqual(12)
|
->multiply(3, 4)->not->toBeString->toEqual(12)
|
||||||
->attributes()->toBeArray()
|
->attributes()->toBeArray()
|
||||||
->books()->toBeArray->each->not->toBeEmpty
|
->books()->toBeArray->each->not->toBeEmpty
|
||||||
->books()->sequence(
|
->books()->sequence(
|
||||||
function ($book) {
|
function ($book): void {
|
||||||
$book->title->toEqual('Foo')->cost->toEqual(20);
|
$book->title->toEqual('Foo')->cost->toEqual(20);
|
||||||
},
|
},
|
||||||
function ($book) {
|
function ($book): void {
|
||||||
$book->title->toEqual('Bar')->cost->toEqual(30);
|
$book->title->toEqual('Bar')->cost->toEqual(30);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can handle nested method calls', function () {
|
it('can handle nested method calls', function (): void {
|
||||||
expect(new HasMethods)
|
expect(new HasMethods)
|
||||||
->newInstance()->newInstance()->name()->toEqual('Has Methods')->toBeString()
|
->newInstance()->newInstance()->name()->toEqual('Has Methods')->toBeString()
|
||||||
->newInstance()->name()->toEqual('Has Methods')->not->toBeInt
|
->newInstance()->name()->toEqual('Has Methods')->not->toBeInt
|
||||||
@@ -82,7 +81,7 @@ it('works with higher order tests')
|
|||||||
->name()->toEqual('Has Methods')
|
->name()->toEqual('Has Methods')
|
||||||
->books()->each->toBeArray;
|
->books()->each->toBeArray;
|
||||||
|
|
||||||
it('can use the scoped method to lock into the given level for expectations', function () {
|
it('can use the scoped method to lock into the given level for expectations', function (): void {
|
||||||
expect(new HasMethods)
|
expect(new HasMethods)
|
||||||
->attributes()->scoped(fn ($attributes) => $attributes
|
->attributes()->scoped(fn ($attributes) => $attributes
|
||||||
->name->toBe('Has Methods')
|
->name->toBe('Has Methods')
|
||||||
@@ -99,7 +98,7 @@ it('can use the scoped method to lock into the given level for expectations', fu
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works consistently with the json expectation method', function () {
|
it('works consistently with the json expectation method', function (): void {
|
||||||
expect(new HasMethods)
|
expect(new HasMethods)
|
||||||
->jsonString()->json()->id->toBe(1)
|
->jsonString()->json()->id->toBe(1)
|
||||||
->jsonString()->json()->name->toBe('Has Methods')->toBeString()
|
->jsonString()->json()->name->toBe('Has Methods')->toBeString()
|
||||||
@@ -113,22 +112,22 @@ class HasMethods
|
|||||||
return '{ "id": 1, "name": "Has Methods", "quantity": 20 }';
|
return '{ "id": 1, "name": "Has Methods", "quantity": 20 }';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function name()
|
public function name(): string
|
||||||
{
|
{
|
||||||
return 'Has Methods';
|
return 'Has Methods';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function quantity()
|
public function quantity(): int
|
||||||
{
|
{
|
||||||
return 20;
|
return 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function multiply($x, $y)
|
public function multiply($x, $y): int|float
|
||||||
{
|
{
|
||||||
return $x * $y;
|
return $x * $y;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function attributes()
|
public function attributes(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => $this->name(),
|
'name' => $this->name(),
|
||||||
@@ -136,7 +135,7 @@ class HasMethods
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function books()
|
public function books(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
@@ -150,7 +149,7 @@ class HasMethods
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function newInstance()
|
public function newInstance(): static
|
||||||
{
|
{
|
||||||
return new static;
|
return new static;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
it('can access methods and properties', function () {
|
it('can access methods and properties', function (): void {
|
||||||
expect(new HasMethodsAndProperties)
|
expect(new HasMethodsAndProperties)
|
||||||
->name->toEqual('Has Methods and Properties')->not()->toEqual('bar')
|
->name->toEqual('Has Methods and Properties')->not()->toEqual('bar')
|
||||||
->multiply(3, 4)->not->toBeString->toEqual(12)
|
->multiply(3, 4)->not->toBeString->toEqual(12)
|
||||||
->posts->each(function ($post) {
|
->posts->each(function ($post): void {
|
||||||
$post->is_published->toBeTrue;
|
$post->is_published->toBeTrue;
|
||||||
})->books()->toBeArray()
|
})->books()->toBeArray()
|
||||||
->posts->toBeArray->each->not->toBeEmpty
|
->posts->toBeArray->each->not->toBeEmpty
|
||||||
->books()->sequence(
|
->books()->sequence(
|
||||||
function ($book) {
|
function ($book): void {
|
||||||
$book->title->toEqual('Foo')->cost->toEqual(20);
|
$book->title->toEqual('Foo')->cost->toEqual(20);
|
||||||
},
|
},
|
||||||
function ($book) {
|
function ($book): void {
|
||||||
$book->title->toEqual('Bar')->cost->toEqual(30);
|
$book->title->toEqual('Bar')->cost->toEqual(30);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can handle nested methods and properties', function () {
|
it('can handle nested methods and properties', function (): void {
|
||||||
expect(new HasMethodsAndProperties)
|
expect(new HasMethodsAndProperties)
|
||||||
->meta->foo->bar->toBeString()->toEqual('baz')->not->toBeInt
|
->meta->foo->bar->toBeString()->toEqual('baz')->not->toBeInt
|
||||||
->newInstance()->meta->foo->toBeArray()
|
->newInstance()->meta->foo->toBeArray()
|
||||||
@@ -33,15 +33,14 @@ it('works with higher order tests')
|
|||||||
->newInstance()->multiply(2, 2)->toEqual(4)->not->toEqual(5)
|
->newInstance()->multiply(2, 2)->toEqual(4)->not->toEqual(5)
|
||||||
->newInstance()->books()->toBeArray();
|
->newInstance()->books()->toBeArray();
|
||||||
|
|
||||||
it('can start a new higher order expectation using the and syntax', function () {
|
it('can start a new higher order expectation using the and syntax', function (): void {
|
||||||
expect(new HasMethodsAndProperties)
|
expect(new HasMethodsAndProperties)
|
||||||
->toBeInstanceOf(HasMethodsAndProperties::class)
|
->toBeInstanceOf(HasMethodsAndProperties::class)
|
||||||
->meta->toBeArray
|
->meta->toBeArray
|
||||||
->and(['foo' => 'bar'])
|
->and(['foo' => 'bar'])
|
||||||
->toBeArray()
|
->toBeArray()
|
||||||
->foo->toEqual('bar');
|
->foo->toEqual('bar')
|
||||||
|
->and(static::getCount())->toEqual(4);
|
||||||
expect(static::getCount())->toEqual(4);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can start a new higher order expectation using the and syntax in higher order tests')
|
it('can start a new higher order expectation using the and syntax in higher order tests')
|
||||||
@@ -52,12 +51,12 @@ it('can start a new higher order expectation using the and syntax in higher orde
|
|||||||
->toBeArray()
|
->toBeArray()
|
||||||
->foo->toEqual('bar');
|
->foo->toEqual('bar');
|
||||||
|
|
||||||
it('can start a new higher order expectation using the and syntax without nesting expectations', function () {
|
it('can start a new higher order expectation using the and syntax without nesting expectations', function (): void {
|
||||||
expect(new HasMethodsAndProperties)
|
expect(new HasMethodsAndProperties)
|
||||||
->toBeInstanceOf(HasMethodsAndProperties::class)
|
->toBeInstanceOf(HasMethodsAndProperties::class)
|
||||||
->meta
|
->meta
|
||||||
->sequence(
|
->sequence(
|
||||||
function ($value, $key) {
|
function ($value, $key): void {
|
||||||
$value->toBeArray()->and($key)->toBe('foo');
|
$value->toBeArray()->and($key)->toBe('foo');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -80,7 +79,7 @@ class HasMethodsAndProperties
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
public function books()
|
public function books(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
@@ -94,12 +93,12 @@ class HasMethodsAndProperties
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function multiply($x, $y)
|
public function multiply($x, $y): int|float
|
||||||
{
|
{
|
||||||
return $x * $y;
|
return $x * $y;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function newInstance()
|
public function newInstance(): static
|
||||||
{
|
{
|
||||||
return new static;
|
return new static;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,50 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
it('allows properties to be accessed from the value', function () {
|
it('allows properties to be accessed from the value', function (): void {
|
||||||
expect(['foo' => 1])->foo->toBeInt()->toEqual(1);
|
expect(['foo' => 1])->foo->toBeInt()->toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can access multiple properties from the value', function () {
|
it('can access multiple properties from the value', function (): void {
|
||||||
expect(['foo' => 'bar', 'hello' => 'world'])
|
expect(['foo' => 'bar', 'hello' => 'world'])
|
||||||
->foo->toBeString()->toEqual('bar')
|
->foo->toBeString()->toEqual('bar')
|
||||||
->hello->toBeString()->toEqual('world');
|
->hello->toBeString()->toEqual('world');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with not', function () {
|
it('works with not', function (): void {
|
||||||
expect(['foo' => 'bar', 'hello' => 'world'])
|
expect(['foo' => 'bar', 'hello' => 'world'])
|
||||||
->foo->not->not->toEqual('bar')
|
->foo->not->not->toEqual('bar')
|
||||||
->foo->not->toEqual('world')->toEqual('bar')
|
->foo->not->toEqual('world')->toEqual('bar')
|
||||||
->hello->toEqual('world')->not()->toEqual('bar')->not->toBeNull;
|
->hello->toEqual('world')->not()->toEqual('bar')->not->toBeNull;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with each', function () {
|
it('works with each', function (): void {
|
||||||
expect(['numbers' => [1, 2, 3, 4], 'words' => ['hey', 'there']])
|
expect(['numbers' => [1, 2, 3, 4], 'words' => ['hey', 'there']])
|
||||||
->numbers->toEqual([1, 2, 3, 4])->each->toBeInt->toBeLessThan(5)
|
->numbers->toEqual([1, 2, 3, 4])->each->toBeInt->toBeLessThan(5)
|
||||||
->words->each(function ($word) {
|
->words->each(function ($word): void {
|
||||||
$word->toBeString()->not->toBeInt();
|
$word->toBeString()->not->toBeInt();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works inside of each', function () {
|
it('works inside of each', function (): void {
|
||||||
expect(['books' => [['title' => 'Foo', 'cost' => 20], ['title' => 'Bar', 'cost' => 30]]])
|
expect(['books' => [['title' => 'Foo', 'cost' => 20], ['title' => 'Bar', 'cost' => 30]]])
|
||||||
->books->each(function ($book) {
|
->books->each(function ($book): void {
|
||||||
$book->title->not->toBeNull->cost->toBeGreaterThan(19);
|
$book->title->not->toBeNull->cost->toBeGreaterThan(19);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with sequence', function () {
|
it('works with sequence', function (): void {
|
||||||
expect(['books' => [['title' => 'Foo', 'cost' => 20], ['title' => 'Bar', 'cost' => 30]]])
|
expect(['books' => [['title' => 'Foo', 'cost' => 20], ['title' => 'Bar', 'cost' => 30]]])
|
||||||
->books->sequence(
|
->books->sequence(
|
||||||
function ($book) {
|
function ($book): void {
|
||||||
$book->title->toEqual('Foo')->cost->toEqual(20);
|
$book->title->toEqual('Foo')->cost->toEqual(20);
|
||||||
},
|
},
|
||||||
function ($book) {
|
function ($book): void {
|
||||||
$book->title->toEqual('Bar')->cost->toEqual(30);
|
$book->title->toEqual('Bar')->cost->toEqual(30);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can compose complex expectations', function () {
|
it('can compose complex expectations', function (): void {
|
||||||
expect(['foo' => 'bar', 'numbers' => [1, 2, 3, 4]])
|
expect(['foo' => 'bar', 'numbers' => [1, 2, 3, 4]])
|
||||||
->toContain('bar')->toBeArray()
|
->toContain('bar')->toBeArray()
|
||||||
->numbers->toEqual([1, 2, 3, 4])->not()->toEqual('bar')->each->toBeInt
|
->numbers->toEqual([1, 2, 3, 4])->not()->toEqual('bar')->each->toBeInt
|
||||||
@@ -52,23 +52,23 @@ it('can compose complex expectations', function () {
|
|||||||
->numbers->toBeArray();
|
->numbers->toBeArray();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with objects', function () {
|
it('works with objects', function (): void {
|
||||||
expect(new HasProperties)
|
expect(new HasProperties)
|
||||||
->name->toEqual('foo')->not->toEqual('world')
|
->name->toEqual('foo')->not->toEqual('world')
|
||||||
->posts->toHaveCount(2)->each(function ($post) {
|
->posts->toHaveCount(2)->each(function ($post): void {
|
||||||
$post->is_published->toBeTrue();
|
$post->is_published->toBeTrue();
|
||||||
})
|
})
|
||||||
->posts->sequence(
|
->posts->sequence(
|
||||||
function ($post) {
|
function ($post): void {
|
||||||
$post->title->toEqual('Foo');
|
$post->title->toEqual('Foo');
|
||||||
},
|
},
|
||||||
function ($post) {
|
function ($post): void {
|
||||||
$post->title->toEqual('Bar');
|
$post->title->toEqual('Bar');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works with nested properties', function () {
|
it('works with nested properties', function (): void {
|
||||||
expect(new HasProperties)
|
expect(new HasProperties)
|
||||||
->nested->foo->bar->toBeString()->toEqual('baz')
|
->nested->foo->bar->toBeString()->toEqual('baz')
|
||||||
->posts->toBeArray()->toHaveCount(2);
|
->posts->toBeArray()->toHaveCount(2);
|
||||||
|
|||||||
@@ -2,16 +2,15 @@
|
|||||||
|
|
||||||
use Pest\Expectation;
|
use Pest\Expectation;
|
||||||
|
|
||||||
test('an exception is thrown if the the type is not iterable', function () {
|
test('an exception is thrown if the the type is not iterable', function (): void {
|
||||||
expect('Foobar')->each()->toEqual('Foobar');
|
expect('Foobar')->each()->toEqual('Foobar');
|
||||||
})->throws(BadMethodCallException::class, 'Expectation value is not iterable.');
|
})->throws(BadMethodCallException::class, 'Expectation value is not iterable.');
|
||||||
|
|
||||||
it('expects on each item', function () {
|
it('expects on each item', function (): void {
|
||||||
expect([1, 1, 1])
|
expect([1, 1, 1])
|
||||||
->each()
|
->each()
|
||||||
->toEqual(1);
|
->toEqual(1)
|
||||||
|
->and(static::getCount())->toBe(3); // + 1 assertion
|
||||||
expect(static::getCount())->toBe(3); // + 1 assertion
|
|
||||||
|
|
||||||
expect([1, 1, 1])
|
expect([1, 1, 1])
|
||||||
->each
|
->each
|
||||||
@@ -20,13 +19,12 @@ it('expects on each item', function () {
|
|||||||
expect(static::getCount())->toBe(7);
|
expect(static::getCount())->toBe(7);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('chains expectations on each item', function () {
|
it('chains expectations on each item', function (): void {
|
||||||
expect([1, 1, 1])
|
expect([1, 1, 1])
|
||||||
->each()
|
->each()
|
||||||
->toBeInt()
|
->toBeInt()
|
||||||
->toEqual(1);
|
->toEqual(1)
|
||||||
|
->and(static::getCount())->toBe(6); // + 1 assertion
|
||||||
expect(static::getCount())->toBe(6); // + 1 assertion
|
|
||||||
|
|
||||||
expect([2, 2, 2])
|
expect([2, 2, 2])
|
||||||
->each
|
->each
|
||||||
@@ -36,13 +34,12 @@ it('chains expectations on each item', function () {
|
|||||||
expect(static::getCount())->toBe(13);
|
expect(static::getCount())->toBe(13);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('opposite expectations on each item', function () {
|
test('opposite expectations on each item', function (): void {
|
||||||
expect([1, 2, 3])
|
expect([1, 2, 3])
|
||||||
->each()
|
->each()
|
||||||
->not()
|
->not()
|
||||||
->toEqual(4);
|
->toEqual(4)
|
||||||
|
->and(static::getCount())->toBe(3);
|
||||||
expect(static::getCount())->toBe(3);
|
|
||||||
|
|
||||||
expect([1, 2, 3])
|
expect([1, 2, 3])
|
||||||
->each()
|
->each()
|
||||||
@@ -51,17 +48,16 @@ test('opposite expectations on each item', function () {
|
|||||||
expect(static::getCount())->toBe(7);
|
expect(static::getCount())->toBe(7);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('chained opposite and non-opposite expectations', function () {
|
test('chained opposite and non-opposite expectations', function (): void {
|
||||||
expect([1, 2, 3])
|
expect([1, 2, 3])
|
||||||
->each()
|
->each()
|
||||||
->not()
|
->not()
|
||||||
->toEqual(4)
|
->toEqual(4)
|
||||||
->toBeInt();
|
->toBeInt()
|
||||||
|
->and(static::getCount())->toBe(6);
|
||||||
expect(static::getCount())->toBe(6);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can add expectations via "and"', function () {
|
it('can add expectations via "and"', function (): void {
|
||||||
expect([1, 2, 3])
|
expect([1, 2, 3])
|
||||||
->each()
|
->each()
|
||||||
->toBeInt // + 3
|
->toBeInt // + 3
|
||||||
@@ -78,20 +74,18 @@ it('can add expectations via "and"', function () {
|
|||||||
expect(static::getCount())->toBe(14);
|
expect(static::getCount())->toBe(14);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accepts callables', function () {
|
it('accepts callables', function (): void {
|
||||||
expect([1, 2, 3])->each(function ($number) {
|
expect([1, 2, 3])->each(function ($number): void {
|
||||||
expect($number)->toBeInstanceOf(Expectation::class);
|
expect($number)->toBeInstanceOf(Expectation::class)
|
||||||
expect($number->value)->toBeInt();
|
->and($number->value)->toBeInt();
|
||||||
$number->toBeInt->not->toBeString;
|
$number->toBeInt->not->toBeString;
|
||||||
|
})
|
||||||
|
->and(static::getCount())->toBe(12);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(static::getCount())->toBe(12);
|
it('passes the key of the current item to callables', function (): void {
|
||||||
});
|
expect([1, 2, 3])->each(function ($number, $key): void {
|
||||||
|
|
||||||
it('passes the key of the current item to callables', function () {
|
|
||||||
expect([1, 2, 3])->each(function ($number, $key) {
|
|
||||||
expect($key)->toBeInt();
|
expect($key)->toBeInt();
|
||||||
});
|
})
|
||||||
|
->and(static::getCount())->toBe(3);
|
||||||
expect(static::getCount())->toBe(3);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
expect()->extend('toBeAMacroExpectation', function () {
|
expect()->extend('toBeAMacroExpectation', function (): object {
|
||||||
$this->toBeTrue();
|
$this->toBeTrue();
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
});
|
});
|
||||||
|
|
||||||
expect()->extend('toBeAMacroExpectationWithArguments', function (bool $value) {
|
expect()->extend('toBeAMacroExpectationWithArguments', function (bool $value): object {
|
||||||
$this->toBe($value);
|
$this->toBe($value);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('macros true is true', function () {
|
it('macros true is true', function (): void {
|
||||||
expect(true)->toBeAMacroExpectation();
|
expect(true)->toBeAMacroExpectation();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('macros false is not true', function () {
|
it('macros false is not true', function (): void {
|
||||||
expect(false)->not->toBeAMacroExpectation();
|
expect(false)->not->toBeAMacroExpectation();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('macros true is true with argument', function () {
|
it('macros true is true with argument', function (): void {
|
||||||
expect(true)->toBeAMacroExpectationWithArguments(true);
|
expect(true)->toBeAMacroExpectationWithArguments(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('macros false is not true with argument', function () {
|
it('macros false is not true with argument', function (): void {
|
||||||
expect(false)->not->toBeAMacroExpectationWithArguments(true);
|
expect(false)->not->toBeAMacroExpectationWithArguments(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
use PHPUnit\Framework\ExpectationFailedException;
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
test('it properly parses json string', function () {
|
test('it properly parses json string', function (): void {
|
||||||
expect('{"name":"Nuno"}')
|
expect('{"name":"Nuno"}')
|
||||||
->json()
|
->json()
|
||||||
->name
|
->name
|
||||||
->toBe('Nuno');
|
->toBe('Nuno');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fails with broken json string', function () {
|
test('fails with broken json string', function (): void {
|
||||||
expect('{":"Nuno"}')->json();
|
expect('{":"Nuno"}')->json();
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
use PHPUnit\Framework\ExpectationFailedException;
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$this->matched = null;
|
$this->matched = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pass', function () {
|
it('pass', function (): void {
|
||||||
expect('baz')
|
expect('baz')
|
||||||
->match('foo', [
|
->match('foo', [
|
||||||
'bar' => function ($value) {
|
'bar' => function ($value) {
|
||||||
@@ -21,25 +21,20 @@ it('pass', function () {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
->toEqual($this->matched);
|
->toEqual($this->matched)
|
||||||
|
->and(static::getCount())->toBe(2);
|
||||||
expect(static::getCount())->toBe(2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('failures', function () {
|
it('failures', function (): void {
|
||||||
expect(true)
|
expect(true)
|
||||||
->match('foo', [
|
->match('foo', [
|
||||||
'bar' => function ($value) {
|
'bar' => fn ($value) => $value->toBeTrue(),
|
||||||
return $value->toBeTrue();
|
'foo' => fn ($value) => $value->toBeFalse(),
|
||||||
},
|
|
||||||
'foo' => function ($value) {
|
|
||||||
return $value->toBeFalse();
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
})->throws(ExpectationFailedException::class, 'true is false');
|
})->throws(ExpectationFailedException::class, 'true is false');
|
||||||
|
|
||||||
it('runs with truthy', function () {
|
it('runs with truthy', function (): void {
|
||||||
expect('foo')
|
expect('foo')
|
||||||
->match(1, [
|
->match(1, [
|
||||||
'bar' => function ($value) {
|
'bar' => function ($value) {
|
||||||
@@ -54,12 +49,11 @@ it('runs with truthy', function () {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
->toEqual($this->matched);
|
->toEqual($this->matched)
|
||||||
|
->and(static::getCount())->toBe(2);
|
||||||
expect(static::getCount())->toBe(2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('runs with falsy', function () {
|
it('runs with falsy', function (): void {
|
||||||
expect('foo')
|
expect('foo')
|
||||||
->match(false, [
|
->match(false, [
|
||||||
'bar' => function ($value) {
|
'bar' => function ($value) {
|
||||||
@@ -74,17 +68,14 @@ it('runs with falsy', function () {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
->toEqual($this->matched);
|
->toEqual($this->matched)
|
||||||
|
->and(static::getCount())->toBe(2);
|
||||||
expect(static::getCount())->toBe(2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('runs with truthy closure condition', function () {
|
it('runs with truthy closure condition', function (): void {
|
||||||
expect('foo')
|
expect('foo')
|
||||||
->match(
|
->match(
|
||||||
function () {
|
fn (): string => '1', [
|
||||||
return '1';
|
|
||||||
}, [
|
|
||||||
'bar' => function ($value) {
|
'bar' => function ($value) {
|
||||||
$this->matched = 'bar';
|
$this->matched = 'bar';
|
||||||
|
|
||||||
@@ -97,17 +88,14 @@ it('runs with truthy closure condition', function () {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
->toEqual($this->matched);
|
->toEqual($this->matched)
|
||||||
|
->and(static::getCount())->toBe(2);
|
||||||
expect(static::getCount())->toBe(2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('runs with falsy closure condition', function () {
|
it('runs with falsy closure condition', function (): void {
|
||||||
expect('foo')
|
expect('foo')
|
||||||
->match(
|
->match(
|
||||||
function () {
|
fn (): string => '0', [
|
||||||
return '0';
|
|
||||||
}, [
|
|
||||||
'bar' => function ($value) {
|
'bar' => function ($value) {
|
||||||
$this->matched = 'bar';
|
$this->matched = 'bar';
|
||||||
|
|
||||||
@@ -120,12 +108,11 @@ it('runs with falsy closure condition', function () {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
->toEqual($this->matched);
|
->toEqual($this->matched)
|
||||||
|
->and(static::getCount())->toBe(2);
|
||||||
expect(static::getCount())->toBe(2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can be passed non-callable values', function () {
|
it('can be passed non-callable values', function (): void {
|
||||||
expect('foo')
|
expect('foo')
|
||||||
->match('pest', [
|
->match('pest', [
|
||||||
'bar' => 'foo',
|
'bar' => 'foo',
|
||||||
@@ -134,21 +121,15 @@ it('can be passed non-callable values', function () {
|
|||||||
);
|
);
|
||||||
})->throws(ExpectationFailedException::class, 'two strings are equal');
|
})->throws(ExpectationFailedException::class, 'two strings are equal');
|
||||||
|
|
||||||
it('fails with unhandled match', function () {
|
it('fails with unhandled match', function (): void {
|
||||||
expect('foo')->match('bar', []);
|
expect('foo')->match('bar', []);
|
||||||
})->throws(ExpectationFailedException::class, 'Unhandled match value.');
|
})->throws(ExpectationFailedException::class, 'Unhandled match value.');
|
||||||
|
|
||||||
it('can be used in higher order tests')
|
it('can be used in higher order tests')
|
||||||
->expect(true)
|
->expect(true)
|
||||||
->match(
|
->match(
|
||||||
function () {
|
fn (): true => true, [
|
||||||
return true;
|
false => fn ($value) => $value->toBeFalse(),
|
||||||
}, [
|
true => fn ($value) => $value->toBeTrue(),
|
||||||
false => function ($value) {
|
|
||||||
return $value->toBeFalse();
|
|
||||||
},
|
|
||||||
true => function ($value) {
|
|
||||||
return $value->toBeTrue();
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
test('not property calls', function () {
|
test('not property calls', function (): void {
|
||||||
expect(true)
|
expect(true)
|
||||||
->toBeTrue()
|
->toBeTrue()
|
||||||
->not()->toBeFalse()
|
->not()->toBeFalse()
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ $state = new State;
|
|||||||
/*
|
/*
|
||||||
* Overrides toBe to assert two Characters are the same
|
* Overrides toBe to assert two Characters are the same
|
||||||
*/
|
*/
|
||||||
expect()->pipe('toBe', function ($next, $expected) use ($state) {
|
expect()->pipe('toBe', function ($next, $expected) use ($state): void {
|
||||||
$state->runCount['char']++;
|
$state->runCount['char']++;
|
||||||
|
|
||||||
if ($this->value instanceof Char) {
|
if ($this->value instanceof Char) {
|
||||||
@@ -81,7 +81,7 @@ expect()->pipe('toBe', function ($next, $expected) use ($state) {
|
|||||||
/*
|
/*
|
||||||
* Overrides toBe to assert two Number objects are the same
|
* Overrides toBe to assert two Number objects are the same
|
||||||
*/
|
*/
|
||||||
expect()->intercept('toBe', Number::class, function ($expected) use ($state) {
|
expect()->intercept('toBe', Number::class, function ($expected) use ($state): void {
|
||||||
$state->runCount['number']++;
|
$state->runCount['number']++;
|
||||||
$state->appliedCount['number']++;
|
$state->appliedCount['number']++;
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ expect()->intercept('toBe', Number::class, function ($expected) use ($state) {
|
|||||||
/*
|
/*
|
||||||
* Overrides toBe to assert all integers are allowed if value is a wildcard (*)
|
* Overrides toBe to assert all integers are allowed if value is a wildcard (*)
|
||||||
*/
|
*/
|
||||||
expect()->intercept('toBe', fn ($value, $expected) => $value === '*' && is_numeric($expected), function ($expected) use ($state) {
|
expect()->intercept('toBe', fn ($value, $expected) => $value === '*' && is_numeric($expected), function ($expected) use ($state): void {
|
||||||
$state->runCount['wildcard']++;
|
$state->runCount['wildcard']++;
|
||||||
$state->appliedCount['wildcard']++;
|
$state->appliedCount['wildcard']++;
|
||||||
});
|
});
|
||||||
@@ -100,7 +100,7 @@ expect()->intercept('toBe', fn ($value, $expected) => $value === '*' && is_numer
|
|||||||
/*
|
/*
|
||||||
* Overrides toBe to assert to Symbols are the same
|
* Overrides toBe to assert to Symbols are the same
|
||||||
*/
|
*/
|
||||||
expect()->pipe('toBe', function ($next, $expected) use ($state) {
|
expect()->pipe('toBe', function ($next, $expected) use ($state): void {
|
||||||
$state->runCount['symbol']++;
|
$state->runCount['symbol']++;
|
||||||
|
|
||||||
if ($this->value instanceof Symbol) {
|
if ($this->value instanceof Symbol) {
|
||||||
@@ -117,7 +117,7 @@ expect()->pipe('toBe', function ($next, $expected) use ($state) {
|
|||||||
/*
|
/*
|
||||||
* Overrides toBe to allow ignoring case when checking strings
|
* Overrides toBe to allow ignoring case when checking strings
|
||||||
*/
|
*/
|
||||||
expect()->intercept('toBe', fn ($value) => is_string($value), function ($expected, $ignoreCase = false) {
|
expect()->intercept('toBe', fn ($value) => is_string($value), function ($expected, $ignoreCase = false): void {
|
||||||
if ($ignoreCase) {
|
if ($ignoreCase) {
|
||||||
assertEqualsIgnoringCase($expected, $this->value);
|
assertEqualsIgnoringCase($expected, $this->value);
|
||||||
} else {
|
} else {
|
||||||
@@ -125,7 +125,7 @@ expect()->intercept('toBe', fn ($value) => is_string($value), function ($expecte
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('pipe is applied and can stop pipeline', function () use ($state) {
|
test('pipe is applied and can stop pipeline', function () use ($state): void {
|
||||||
$char = new Char('A');
|
$char = new Char('A');
|
||||||
|
|
||||||
$state->reset();
|
$state->reset();
|
||||||
@@ -146,7 +146,7 @@ test('pipe is applied and can stop pipeline', function () use ($state) {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('pipe is run and can let the pipeline keep going', function () use ($state) {
|
test('pipe is run and can let the pipeline keep going', function () use ($state): void {
|
||||||
$state->reset();
|
$state->reset();
|
||||||
|
|
||||||
expect(3)->toBe(3)
|
expect(3)->toBe(3)
|
||||||
@@ -165,7 +165,7 @@ test('pipe is run and can let the pipeline keep going', function () use ($state)
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('pipe works with negated expectation', function () use ($state) {
|
test('pipe works with negated expectation', function () use ($state): void {
|
||||||
$char = new Char('A');
|
$char = new Char('A');
|
||||||
|
|
||||||
$state->reset();
|
$state->reset();
|
||||||
@@ -186,7 +186,7 @@ test('pipe works with negated expectation', function () use ($state) {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('interceptor is applied', function () use ($state) {
|
test('interceptor is applied', function () use ($state): void {
|
||||||
$number = new Number(1);
|
$number = new Number(1);
|
||||||
|
|
||||||
$state->reset();
|
$state->reset();
|
||||||
@@ -197,7 +197,7 @@ test('interceptor is applied', function () use ($state) {
|
|||||||
->appliedCount->toHaveKey('number', 1);
|
->appliedCount->toHaveKey('number', 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('interceptor stops the pipeline', function () use ($state) {
|
test('interceptor stops the pipeline', function () use ($state): void {
|
||||||
$number = new Number(1);
|
$number = new Number(1);
|
||||||
|
|
||||||
$state->reset();
|
$state->reset();
|
||||||
@@ -218,7 +218,7 @@ test('interceptor stops the pipeline', function () use ($state) {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('interceptor is called only when filter is met', function () use ($state) {
|
test('interceptor is called only when filter is met', function () use ($state): void {
|
||||||
$state->reset();
|
$state->reset();
|
||||||
|
|
||||||
expect(1)->toBe(1)
|
expect(1)->toBe(1)
|
||||||
@@ -227,7 +227,7 @@ test('interceptor is called only when filter is met', function () use ($state) {
|
|||||||
->appliedCount->toHaveKey('number', 0);
|
->appliedCount->toHaveKey('number', 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('interceptor can be filtered with a closure', function () use ($state) {
|
test('interceptor can be filtered with a closure', function () use ($state): void {
|
||||||
$state->reset();
|
$state->reset();
|
||||||
|
|
||||||
expect('*')->toBe(1)
|
expect('*')->toBe(1)
|
||||||
@@ -236,7 +236,7 @@ test('interceptor can be filtered with a closure', function () use ($state) {
|
|||||||
->appliedCount->toHaveKey('wildcard', 1);
|
->appliedCount->toHaveKey('wildcard', 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('interceptor can be filter the expected parameter as well', function () use ($state) {
|
test('interceptor can be filter the expected parameter as well', function () use ($state): void {
|
||||||
$state->reset();
|
$state->reset();
|
||||||
|
|
||||||
expect('*')->toBe('*')
|
expect('*')->toBe('*')
|
||||||
@@ -245,13 +245,13 @@ test('interceptor can be filter the expected parameter as well', function () use
|
|||||||
->appliedCount->toHaveKey('wildcard', 0);
|
->appliedCount->toHaveKey('wildcard', 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('interceptor works with negated expectation', function () {
|
test('interceptor works with negated expectation', function (): void {
|
||||||
$char = new Number(1);
|
$char = new Number(1);
|
||||||
|
|
||||||
expect($char)->not->toBe(new Char('B'));
|
expect($char)->not->toBe(new Char('B'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('intercept can add new parameters to the expectation', function () {
|
test('intercept can add new parameters to the expectation', function (): void {
|
||||||
$ignoreCase = true;
|
$ignoreCase = true;
|
||||||
|
|
||||||
expect('Foo')->toBe('foo', $ignoreCase);
|
expect('Foo')->toBe('foo', $ignoreCase);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
test('ray calls do not fail when ray is not installed', function () {
|
declare(strict_types=1);
|
||||||
expect(true)->ray()->toBe(true);
|
|
||||||
|
test('ray calls do not fail when ray is not installed', function (): void {
|
||||||
|
expect(true)->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,95 +1,91 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
test('an exception is thrown if the the type is not iterable', function () {
|
test('an exception is thrown if the the type is not iterable', function (): void {
|
||||||
expect('Foobar')->each->sequence();
|
expect('Foobar')->each->sequence();
|
||||||
})->throws(BadMethodCallException::class, 'Expectation value is not iterable.');
|
})->throws(BadMethodCallException::class, 'Expectation value is not iterable.');
|
||||||
|
|
||||||
test('an exception is thrown if there are no expectations', function () {
|
test('an exception is thrown if there are no expectations', function (): void {
|
||||||
expect(['Foobar'])->sequence();
|
expect(['Foobar'])->sequence();
|
||||||
})->throws(InvalidArgumentException::class, 'No sequence expectations defined.');
|
})->throws(InvalidArgumentException::class, 'No sequence expectations defined.');
|
||||||
|
|
||||||
test('allows for sequences of checks to be run on iterable data', function () {
|
test('allows for sequences of checks to be run on iterable data', function (): void {
|
||||||
expect([1, 2, 3])
|
expect([1, 2, 3])
|
||||||
->sequence(
|
->sequence(
|
||||||
function ($expectation) {
|
function ($expectation): void {
|
||||||
$expectation->toBeInt()->toEqual(1);
|
$expectation->toBeInt()->toEqual(1);
|
||||||
},
|
},
|
||||||
function ($expectation) {
|
function ($expectation): void {
|
||||||
$expectation->toBeInt()->toEqual(2);
|
$expectation->toBeInt()->toEqual(2);
|
||||||
},
|
},
|
||||||
function ($expectation) {
|
function ($expectation): void {
|
||||||
$expectation->toBeInt()->toEqual(3);
|
$expectation->toBeInt()->toEqual(3);
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
->and(static::getCount())->toBe(6);
|
||||||
expect(static::getCount())->toBe(6);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('loops back to the start if it runs out of sequence items', function () {
|
test('loops back to the start if it runs out of sequence items', function (): void {
|
||||||
expect([1, 2, 3, 1, 2, 3, 1, 2])
|
expect([1, 2, 3, 1, 2, 3, 1, 2])
|
||||||
->sequence(
|
->sequence(
|
||||||
function ($expectation) {
|
function ($expectation): void {
|
||||||
$expectation->toBeInt()->toEqual(1);
|
$expectation->toBeInt()->toEqual(1);
|
||||||
},
|
},
|
||||||
function ($expectation) {
|
function ($expectation): void {
|
||||||
$expectation->toBeInt()->toEqual(2);
|
$expectation->toBeInt()->toEqual(2);
|
||||||
},
|
},
|
||||||
function ($expectation) {
|
function ($expectation): void {
|
||||||
$expectation->toBeInt()->toEqual(3);
|
$expectation->toBeInt()->toEqual(3);
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
->and(static::getCount())->toBe(16);
|
||||||
expect(static::getCount())->toBe(16);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fails if the number of iterable items is less than the number of expectations', function () {
|
test('fails if the number of iterable items is less than the number of expectations', function (): void {
|
||||||
expect([1, 2])
|
expect([1, 2])
|
||||||
->sequence(
|
->sequence(
|
||||||
function ($expectation) {
|
function ($expectation): void {
|
||||||
$expectation->toBeInt()->toEqual(1);
|
$expectation->toBeInt()->toEqual(1);
|
||||||
},
|
},
|
||||||
function ($expectation) {
|
function ($expectation): void {
|
||||||
$expectation->toBeInt()->toEqual(2);
|
$expectation->toBeInt()->toEqual(2);
|
||||||
},
|
},
|
||||||
function ($expectation) {
|
function ($expectation): void {
|
||||||
$expectation->toBeInt()->toEqual(3);
|
$expectation->toBeInt()->toEqual(3);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
})->throws(OutOfRangeException::class, 'Sequence expectations are more than the iterable items.');
|
})->throws(OutOfRangeException::class, 'Sequence expectations are more than the iterable items.');
|
||||||
|
|
||||||
test('it works with associative arrays', function () {
|
test('it works with associative arrays', function (): void {
|
||||||
expect(['foo' => 'bar', 'baz' => 'boom'])
|
expect(['foo' => 'bar', 'baz' => 'boom'])
|
||||||
->sequence(
|
->sequence(
|
||||||
function ($expectation, $key) {
|
function ($expectation, $key): void {
|
||||||
$expectation->toEqual('bar');
|
$expectation->toEqual('bar');
|
||||||
$key->toEqual('foo');
|
$key->toEqual('foo');
|
||||||
},
|
},
|
||||||
function ($expectation, $key) {
|
function ($expectation, $key): void {
|
||||||
$expectation->toEqual('boom');
|
$expectation->toEqual('boom');
|
||||||
$key->toEqual('baz');
|
$key->toEqual('baz');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it can be passed non-callable values', function () {
|
test('it can be passed non-callable values', function (): void {
|
||||||
expect(['foo', 'bar', 'baz'])->sequence('foo', 'bar', 'baz');
|
expect(['foo', 'bar', 'baz'])->sequence('foo', 'bar', 'baz')
|
||||||
|
->and(static::getCount())->toBe(3);
|
||||||
expect(static::getCount())->toBe(3);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it can be passed a mixture of value types', function () {
|
test('it can be passed a mixture of value types', function (): void {
|
||||||
expect(['foo', 'bar', 'baz'])->sequence(
|
expect(['foo', 'bar', 'baz'])->sequence(
|
||||||
'foo',
|
'foo',
|
||||||
function ($expectation) {
|
function ($expectation): void {
|
||||||
$expectation->toEqual('bar')->toBeString();
|
$expectation->toEqual('bar')->toBeString();
|
||||||
},
|
},
|
||||||
'baz'
|
'baz'
|
||||||
);
|
)
|
||||||
|
->and(static::getCount())->toBe(4);
|
||||||
expect(static::getCount())->toBe(4);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it works with traversables', function () {
|
test('it works with traversables', function (): void {
|
||||||
$generator = (function () {
|
$generator = (function () {
|
||||||
yield 'one' => (fn () => yield from [1, 2, 3])();
|
yield 'one' => (fn () => yield from [1, 2, 3])();
|
||||||
yield 'two' => (fn () => yield from [4, 5, 6])();
|
yield 'two' => (fn () => yield from [4, 5, 6])();
|
||||||
|
|||||||
@@ -4,21 +4,21 @@ use PHPUnit\Framework\ExpectationFailedException;
|
|||||||
|
|
||||||
expect(true)->toBeTrue()->and(false)->toBeFalse();
|
expect(true)->toBeTrue()->and(false)->toBeFalse();
|
||||||
|
|
||||||
test('strict comparisons', function () {
|
test('strict comparisons', function (): void {
|
||||||
$nuno = new stdClass;
|
$nuno = new stdClass;
|
||||||
$dries = new stdClass;
|
$dries = new stdClass;
|
||||||
|
|
||||||
expect($nuno)->toBe($nuno)->not->toBe($dries);
|
expect($nuno)->toBe($nuno)->not->toBe($dries);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('failures', function () {
|
test('failures', function (): void {
|
||||||
expect(1)->toBe(2);
|
expect(1)->toBe(2);
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
test('failures with custom message', function () {
|
test('failures with custom message', function (): void {
|
||||||
expect(1)->toBe(2, 'oh no!');
|
expect(1)->toBe(2, 'oh no!');
|
||||||
})->throws(ExpectationFailedException::class, 'oh no!');
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
test('not failures', function () {
|
test('not failures', function (): void {
|
||||||
expect(1)->not->toBe(1);
|
expect(1)->not->toBe(1);
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\ExpectationFailedException;
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
test('pass', function () {
|
test('pass', function (): void {
|
||||||
expect('abc')->toBeAlpha();
|
expect('abc')->toBeAlpha()
|
||||||
expect('123')->not->toBeAlpha();
|
->and('123')->not->toBeAlpha();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('failures', function () {
|
test('failures', function (): void {
|
||||||
expect('123')->toBeAlpha();
|
expect('123')->toBeAlpha();
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
test('failures with custom message', function () {
|
test('failures with custom message', function (): void {
|
||||||
expect('123')->toBeAlpha('oh no!');
|
expect('123')->toBeAlpha('oh no!');
|
||||||
})->throws(ExpectationFailedException::class, 'oh no!');
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
test('not failures', function () {
|
test('not failures', function (): void {
|
||||||
expect('abc')->not->toBeAlpha();
|
expect('abc')->not->toBeAlpha();
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\ExpectationFailedException;
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
test('pass', function () {
|
test('pass', function (): void {
|
||||||
expect('abc123')->toBeAlphaNumeric();
|
expect('abc123')->toBeAlphaNumeric()
|
||||||
expect('-')->not->toBeAlphaNumeric();
|
->and('-')->not->toBeAlphaNumeric();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('failures', function () {
|
test('failures', function (): void {
|
||||||
expect('-')->toBeAlphaNumeric();
|
expect('-')->toBeAlphaNumeric();
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
test('failures with custom message', function () {
|
test('failures with custom message', function (): void {
|
||||||
expect('-')->toBeAlphaNumeric('oh no!');
|
expect('-')->toBeAlphaNumeric('oh no!');
|
||||||
})->throws(ExpectationFailedException::class, 'oh no!');
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
test('not failures', function () {
|
test('not failures', function (): void {
|
||||||
expect('abc123')->not->toBeAlphaNumeric();
|
expect('abc123')->not->toBeAlphaNumeric();
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\ExpectationFailedException;
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
test('pass', function () {
|
test('pass', function (): void {
|
||||||
expect([1, 2, 3])->toBeArray();
|
expect([1, 2, 3])->toBeArray()
|
||||||
expect('1, 2, 3')->not->toBeArray();
|
->and('1, 2, 3')->not->toBeArray();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('failures', function () {
|
test('failures', function (): void {
|
||||||
expect(null)->toBeArray();
|
expect()->toBeArray();
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
test('failures with custom message', function () {
|
test('failures with custom message', function (): void {
|
||||||
expect(null)->toBeArray('oh no!');
|
expect()->toBeArray('oh no!');
|
||||||
})->throws(ExpectationFailedException::class, 'oh no!');
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
test('not failures', function () {
|
test('not failures', function (): void {
|
||||||
expect(['a', 'b', 'c'])->not->toBeArray();
|
expect(['a', 'b', 'c'])->not->toBeArray();
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Pest\Exceptions\InvalidExpectationValue;
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
|
test('pass', function (): void {
|
||||||
|
expect('Zm9v')->toBeBase64() // 'foo' (no padding)
|
||||||
|
->and('Zm9vYg==')->toBeBase64() // 'foob' (with padding)
|
||||||
|
->and('Zm9vYmE=')->toBeBase64() // 'fooba' (with padding)
|
||||||
|
->and('Zm9vYmFy')->toBeBase64() // 'foobar' (no padding)
|
||||||
|
->and('')->toBeBase64(); // empty string
|
||||||
|
});
|
||||||
|
|
||||||
|
test('failures', function (): void {
|
||||||
|
expect('not-base64!')->toBeBase64();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid padding', function (): void {
|
||||||
|
expect('AAA')->toBeBase64();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with padding only', function (): void {
|
||||||
|
expect('====')->toBeBase64();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with malformed input', function (): void {
|
||||||
|
expect('!!invalid!!')->toBeBase64();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
|
test('failures with invalid type', function (): void {
|
||||||
|
expect([])->toBeBase64();
|
||||||
|
})->throws(InvalidExpectationValue::class, 'Invalid expectation value type. Expected [string].');
|
||||||
|
|
||||||
|
test('failures with custom message', function (): void {
|
||||||
|
expect('!!invalid!!')->toBeBase64('oh no!');
|
||||||
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
|
test('not failures', function (): void {
|
||||||
|
expect('Zm9v')->not->toBeBase64();
|
||||||
|
})->throws(ExpectationFailedException::class);
|
||||||
@@ -1,43 +1,45 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\ExpectationFailedException;
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
test('passes with int', function () {
|
test('passes with int', function (): void {
|
||||||
expect(2)->toBeBetween(1, 3);
|
expect(2)->toBeBetween(1, 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('passes with float', function () {
|
test('passes with float', function (): void {
|
||||||
expect(1.5)->toBeBetween(1.25, 1.75);
|
expect(1.5)->toBeBetween(1.25, 1.75);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('passes with float and int', function () {
|
test('passes with float and int', function (): void {
|
||||||
expect(1.5)->toBeBetween(1, 2);
|
expect(1.5)->toBeBetween(1, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('passes with DateTime', function () {
|
test('passes with DateTime', function (): void {
|
||||||
expect(new DateTime('2023-09-22'))->toBeBetween(new DateTime('2023-09-21'), new DateTime('2023-09-23'));
|
expect(new DateTime('2023-09-22'))->toBeBetween(new DateTime('2023-09-21'), new DateTime('2023-09-23'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('failure with int', function () {
|
test('failure with int', function (): void {
|
||||||
expect(4)->toBeBetween(1, 3);
|
expect(4)->toBeBetween(1, 3);
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
test('failure with float', function () {
|
test('failure with float', function (): void {
|
||||||
expect(2)->toBeBetween(1.5, 1.75);
|
expect(2)->toBeBetween(1.5, 1.75);
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
test('failure with float and int', function () {
|
test('failure with float and int', function (): void {
|
||||||
expect(2.1)->toBeBetween(1, 2);
|
expect(2.1)->toBeBetween(1, 2);
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
test('failure with DateTime', function () {
|
test('failure with DateTime', function (): void {
|
||||||
expect(new DateTime('2023-09-20'))->toBeBetween(new DateTime('2023-09-21'), new DateTime('2023-09-23'));
|
expect(new DateTime('2023-09-20'))->toBeBetween(new DateTime('2023-09-21'), new DateTime('2023-09-23'));
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
test('failures with custom message', function () {
|
test('failures with custom message', function (): void {
|
||||||
expect(4)->toBeBetween(1, 3, 'oh no!');
|
expect(4)->toBeBetween(1, 3, 'oh no!');
|
||||||
})->throws(ExpectationFailedException::class, 'oh no!');
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
test('not failures', function () {
|
test('not failures', function (): void {
|
||||||
expect(2)->not->toBeBetween(1, 3);
|
expect(2)->not->toBeBetween(1, 3);
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\ExpectationFailedException;
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
test('pass', function () {
|
test('pass', function (): void {
|
||||||
expect(true)->toBeBool();
|
expect(true)->toBeBool()
|
||||||
expect(0)->not->toBeBool();
|
->and(0)->not->toBeBool();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('failures', function () {
|
test('failures', function (): void {
|
||||||
expect(null)->toBeBool();
|
expect()->toBeBool();
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
test('failures with custom message', function () {
|
test('failures with custom message', function (): void {
|
||||||
expect(null)->toBeBool('oh no!');
|
expect()->toBeBool('oh no!');
|
||||||
})->throws(ExpectationFailedException::class, 'oh no!');
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
test('not failures', function () {
|
test('not failures', function (): void {
|
||||||
expect(false)->not->toBeBool();
|
expect(false)->not->toBeBool();
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\ExpectationFailedException;
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
test('pass', function () {
|
test('pass', function (): void {
|
||||||
expect(function () {})->toBeCallable();
|
expect(function (): void {})->toBeCallable()
|
||||||
expect(null)->not->toBeCallable();
|
->and(null)->not->toBeCallable();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('failures', function () {
|
test('failures', function (): void {
|
||||||
$hello = 5;
|
$hello = 5;
|
||||||
|
|
||||||
expect($hello)->toBeCallable();
|
expect($hello)->toBeCallable();
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
test('failures with custom message', function () {
|
test('failures with custom message', function (): void {
|
||||||
$hello = 5;
|
$hello = 5;
|
||||||
|
|
||||||
expect($hello)->toBeCallable('oh no!');
|
expect($hello)->toBeCallable('oh no!');
|
||||||
})->throws(ExpectationFailedException::class, 'oh no!');
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
test('not failures', function () {
|
test('not failures', function (): void {
|
||||||
expect(function () {
|
expect(fn (): int => 42)->not->toBeCallable();
|
||||||
return 42;
|
|
||||||
})->not->toBeCallable();
|
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\ExpectationFailedException;
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
test('pass', function () {
|
test('pass', function (): void {
|
||||||
expect('abc')->toBeCamelCase();
|
expect('abc')->toBeCamelCase()
|
||||||
expect('abcDef')->toBeCamelCase();
|
->and('abcDef')->toBeCamelCase()
|
||||||
expect('abc-def')->not->toBeCamelCase();
|
->and('abc-def')->not->toBeCamelCase()->not->toBeCamelCase()
|
||||||
expect('abc-def')->not->toBeCamelCase();
|
->and('AbcDef')->not->toBeCamelCase();
|
||||||
expect('AbcDef')->not->toBeCamelCase();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('failures', function () {
|
test('failures', function (): void {
|
||||||
expect('Abc')->toBeCamelCase();
|
expect('Abc')->toBeCamelCase();
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
test('failures with custom message', function () {
|
test('failures with custom message', function (): void {
|
||||||
expect('Abc')->toBeCamelCase('oh no!');
|
expect('Abc')->toBeCamelCase('oh no!');
|
||||||
})->throws(ExpectationFailedException::class, 'oh no!');
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
test('not failures', function () {
|
test('not failures', function (): void {
|
||||||
expect('abcDef')->not->toBeCamelCase();
|
expect('abcDef')->not->toBeCamelCase();
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\ExpectationFailedException;
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
test('pass', function () {
|
test('pass', function (): void {
|
||||||
expect('123')->toBeDigits();
|
expect('123')->toBeDigits()
|
||||||
expect('123.14')->not->toBeDigits();
|
->and('123.14')->not->toBeDigits();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('failures', function () {
|
test('failures', function (): void {
|
||||||
expect('123.14')->toBeDigits();
|
expect('123.14')->toBeDigits();
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
test('failures with custom message', function () {
|
test('failures with custom message', function (): void {
|
||||||
expect('123.14')->toBeDigits('oh no!');
|
expect('123.14')->toBeDigits('oh no!');
|
||||||
})->throws(ExpectationFailedException::class, 'oh no!');
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
test('not failures', function () {
|
test('not failures', function (): void {
|
||||||
expect('445')->not->toBeDigits();
|
expect('445')->not->toBeDigits();
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use PHPUnit\Framework\ExpectationFailedException;
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
|
||||||
test('pass', function () {
|
test('pass', function (): void {
|
||||||
$temp = sys_get_temp_dir();
|
$temp = sys_get_temp_dir();
|
||||||
|
|
||||||
expect($temp)->toBeDirectory();
|
expect($temp)->toBeDirectory();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('failures', function () {
|
test('failures', function (): void {
|
||||||
expect('/random/path/whatever')->toBeDirectory();
|
expect('/random/path/whatever')->toBeDirectory();
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|
||||||
test('failures with custom message', function () {
|
test('failures with custom message', function (): void {
|
||||||
expect('/random/path/whatever')->toBeDirectory('oh no!');
|
expect('/random/path/whatever')->toBeDirectory('oh no!');
|
||||||
})->throws(ExpectationFailedException::class, 'oh no!');
|
})->throws(ExpectationFailedException::class, 'oh no!');
|
||||||
|
|
||||||
test('not failures', function () {
|
test('not failures', function (): void {
|
||||||
expect('.')->not->toBeDirectory();
|
expect('.')->not->toBeDirectory();
|
||||||
})->throws(ExpectationFailedException::class);
|
})->throws(ExpectationFailedException::class);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user