Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
154 commits
Select commit Hold shift + click to select a range
c440031
feat(tia): adds poc
nunomaduro Apr 16, 2026
4b9bb77
feat(tia): adds poc
nunomaduro Apr 16, 2026
1d81069
feat(tia): continues to work on poc
nunomaduro Apr 16, 2026
184f5d2
feat(tia): continues to work on poc
nunomaduro Apr 16, 2026
f52a455
fix
nunomaduro Apr 16, 2026
494cc6e
feat(tia): continues to work on poc
nunomaduro Apr 16, 2026
f09d6f2
feat(tia): continues to work on poc
nunomaduro Apr 16, 2026
d379128
feat(tia): continues to work on poc
nunomaduro Apr 16, 2026
c7e32f5
feat(tia): continues to work on poc
nunomaduro Apr 16, 2026
42d1092
feat(tia): continues to work on poc
nunomaduro Apr 16, 2026
9c8033d
feat(tia): continues to work on poc
nunomaduro Apr 16, 2026
47f1fc2
feat(tia): continues to work on poc
nunomaduro Apr 20, 2026
8c849c5
fix: missing warning
nunomaduro Apr 20, 2026
980667e
wip
nunomaduro Apr 20, 2026
adc5aae
wip
nunomaduro Apr 20, 2026
0d99c33
wip
nunomaduro Apr 20, 2026
55a3394
wip
nunomaduro Apr 20, 2026
59e781e
wip
nunomaduro Apr 20, 2026
2892341
wip
nunomaduro Apr 20, 2026
1476b52
wip
nunomaduro Apr 21, 2026
a5915b1
wip
nunomaduro Apr 21, 2026
7e4280b
chore: improves feedback
nunomaduro Apr 21, 2026
0d66dc4
chore: removes https
nunomaduro Apr 21, 2026
ed399af
wip
nunomaduro Apr 21, 2026
2941f98
wip
nunomaduro Apr 21, 2026
f6609f4
wip
nunomaduro Apr 21, 2026
51fc380
wip
nunomaduro Apr 21, 2026
e24882c
wip
nunomaduro Apr 21, 2026
856a370
style
nunomaduro Apr 21, 2026
c6a42a2
wip
nunomaduro Apr 22, 2026
68527c9
wip
nunomaduro Apr 22, 2026
660b57b
wip
nunomaduro Apr 22, 2026
d9c18f9
wip
nunomaduro Apr 22, 2026
e876dba
wip
nunomaduro Apr 23, 2026
c1feefb
wip
nunomaduro Apr 23, 2026
470a583
wip
nunomaduro Apr 23, 2026
caabebf
wip
nunomaduro Apr 23, 2026
3d3c5d4
wip
nunomaduro Apr 23, 2026
b46f051
wip
nunomaduro Apr 24, 2026
48357c6
wip
nunomaduro Apr 27, 2026
e457eb0
wip
nunomaduro Apr 27, 2026
7250185
wip
nunomaduro Apr 27, 2026
b9088d2
wip
nunomaduro Apr 27, 2026
f45cbf4
wip
nunomaduro Apr 27, 2026
81bfdbf
wip
nunomaduro Apr 27, 2026
d4c7362
wip
nunomaduro Apr 27, 2026
339c1e8
wip
nunomaduro Apr 27, 2026
f4e22dc
wip
nunomaduro Apr 27, 2026
b944ee5
wip
nunomaduro Apr 27, 2026
405d8d4
wip
nunomaduro Apr 28, 2026
ca2dca5
wup
nunomaduro Apr 28, 2026
00f8d56
wip
nunomaduro Apr 28, 2026
4662596
wip
nunomaduro Apr 28, 2026
95a0034
wip for now
nunomaduro Apr 29, 2026
f355b99
wip
nunomaduro Apr 29, 2026
6a434be
wip
nunomaduro Apr 30, 2026
3c91bf4
wip
nunomaduro Apr 30, 2026
3a26028
wip
nunomaduro Apr 30, 2026
6b59166
wip
nunomaduro Apr 30, 2026
d7735d1
wip
nunomaduro Apr 30, 2026
58dfb6d
wip
nunomaduro Apr 30, 2026
8711d51
fix
nunomaduro Apr 30, 2026
4b8642b
wip
nunomaduro Apr 30, 2026
48b70a0
wip
nunomaduro May 1, 2026
5d9f95f
qwdqwd
nunomaduro May 1, 2026
be34eec
wip
nunomaduro May 1, 2026
30b94e3
qdw
nunomaduro May 1, 2026
07416a3
wip
nunomaduro May 1, 2026
b5bb213
wqdqwd
nunomaduro May 1, 2026
4a2fc17
asd
nunomaduro May 1, 2026
6b9c768
wip
nunomaduro May 1, 2026
5c3cbc1
wip
nunomaduro May 1, 2026
3802fa8
asd
nunomaduro May 1, 2026
0a97d3a
asd
nunomaduro May 1, 2026
fda515a
wip
nunomaduro May 1, 2026
6ac6c15
wip
nunomaduro May 1, 2026
d106b70
wip
nunomaduro May 1, 2026
45b1d4c
wip
nunomaduro May 1, 2026
bed5e5b
wip
nunomaduro May 1, 2026
a725e77
wip
nunomaduro May 1, 2026
a349f53
wip
nunomaduro May 1, 2026
8a51f15
wip
nunomaduro May 1, 2026
97600b6
wip
nunomaduro May 1, 2026
b2c0756
wip
nunomaduro May 1, 2026
6e0e030
wip
nunomaduro May 1, 2026
5c08a13
wip
nunomaduro May 1, 2026
aa3a7c3
wip
nunomaduro May 1, 2026
3bcabfb
fix
nunomaduro May 1, 2026
57fd5ce
wip
nunomaduro May 1, 2026
34f1e9a
fix
nunomaduro May 1, 2026
53db68e
wip
nunomaduro May 1, 2026
bf48e20
wip
nunomaduro May 1, 2026
e59b99c
wip
nunomaduro May 1, 2026
21efbc3
wip
nunomaduro May 1, 2026
d0295f6
wip
nunomaduro May 1, 2026
c4911d0
wip
nunomaduro May 1, 2026
3cc9b16
wip
nunomaduro May 1, 2026
1d3e8bb
wip
nunomaduro May 2, 2026
6e1bf63
wip
nunomaduro May 2, 2026
6407c4f
wip
nunomaduro May 2, 2026
c38d32a
wip
nunomaduro May 2, 2026
872796b
wip
nunomaduro May 2, 2026
9b7c15d
wip
nunomaduro May 2, 2026
631bbe3
wip
nunomaduro May 2, 2026
7d51601
wip
nunomaduro May 2, 2026
4a8c2d7
wip
nunomaduro May 2, 2026
a4e7776
wip
nunomaduro May 2, 2026
348b439
wip
nunomaduro May 2, 2026
460401c
wip
nunomaduro May 2, 2026
925935a
wip
nunomaduro May 2, 2026
5242803
wip
nunomaduro May 2, 2026
28305fc
wip
nunomaduro May 2, 2026
5f37939
wip
nunomaduro May 2, 2026
1c7c975
wip
nunomaduro May 2, 2026
f247dd8
wip
nunomaduro May 2, 2026
8a14056
wip
nunomaduro May 2, 2026
1aa80dc
wip
nunomaduro May 2, 2026
6354606
wip
nunomaduro May 2, 2026
df829ad
wip
nunomaduro May 2, 2026
5cae93b
wip
nunomaduro May 2, 2026
7cbad4c
wip
nunomaduro May 2, 2026
9f804dc
wip
nunomaduro May 2, 2026
57eecb2
wip
nunomaduro May 2, 2026
a07a2e5
wip
nunomaduro May 2, 2026
89f3d6c
wip
nunomaduro May 2, 2026
d00ec95
wip
nunomaduro May 2, 2026
1e48c5d
wip
nunomaduro May 2, 2026
7b1ec9f
wip
nunomaduro May 2, 2026
e3e178f
wip
nunomaduro May 2, 2026
8ddcd3e
wip
nunomaduro May 2, 2026
6add4da
wip
nunomaduro May 2, 2026
31c2007
wip
nunomaduro May 2, 2026
380ccd3
wip
nunomaduro May 2, 2026
e2d940c
wip
nunomaduro May 2, 2026
e4d9b61
wip
nunomaduro May 2, 2026
5b8393b
wip
nunomaduro May 2, 2026
51c8ce4
wip
nunomaduro May 2, 2026
d6db3a8
wip
nunomaduro May 2, 2026
4280233
wip
nunomaduro May 2, 2026
7bea819
wip
nunomaduro May 2, 2026
9e4cf4b
wip
nunomaduro May 2, 2026
65c0fbc
wip
nunomaduro May 2, 2026
536d79f
wip
nunomaduro May 2, 2026
a47e6f8
wip
nunomaduro May 2, 2026
99cc4e0
wip
nunomaduro May 2, 2026
e3004db
wip
nunomaduro May 3, 2026
a3796da
wip
nunomaduro May 3, 2026
fb0978c
wip
nunomaduro May 3, 2026
89590d6
wip
nunomaduro May 3, 2026
75593b6
wip
nunomaduro May 3, 2026
9afbcd5
wuip
nunomaduro May 3, 2026
e1a4b98
wip
nunomaduro May 3, 2026
d3ce498
wip
nunomaduro May 3, 2026
46bc3dc
wip
nunomaduro May 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions bin/pest
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

declare(strict_types=1);

use Pest\Contracts\Restarter;
use Pest\Kernel;
use Pest\Panic;
use Pest\Support\Container;
use Pest\TestCaseFilters\GitDirtyTestCaseFilter;
use Pest\TestCaseMethodFilters\AssigneeTestCaseFilter;
use Pest\TestCaseMethodFilters\IssueTestCaseFilter;
Expand Down Expand Up @@ -142,6 +144,7 @@ use Symfony\Component\Console\Output\ConsoleOutput;

// Get $rootPath based on $autoloadPath
$rootPath = dirname($autoloadPath, 2);

$input = new ArgvInput;

$testSuite = TestSuite::getInstance(
Expand Down Expand Up @@ -192,6 +195,15 @@ use Symfony\Component\Console\Output\ConsoleOutput;
try {
$kernel = Kernel::boot($testSuite, $input, $output);

$container = Container::getInstance();

foreach (Kernel::RESTARTERS as $restarterClass) {
$restarter = $container->get($restarterClass);
assert($restarter instanceof Restarter);

$restarter->maybeRestart($rootPath, $originalArguments);
}

$result = $kernel->handle($originalArguments, $arguments);

$kernel->terminate();
Expand Down
239 changes: 239 additions & 0 deletions bin/pest-tia-vite-deps.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
#!/usr/bin/env node

import { readdir, readFile } from 'node:fs/promises'
import { existsSync } from 'node:fs'
import { createRequire } from 'node:module'
import { resolve, relative, extname, sep, join } from 'node:path'
import { pathToFileURL } from 'node:url'

const PAGE_EXTENSIONS = new Set([
'.vue', '.svelte',
'.tsx', '.jsx',
'.ts', '.js',
'.mts', '.cts', '.mjs', '.cjs',
])
const ASSET_EXT_RE = /\.(css|scss|sass|less|styl|stylus|svg|png|jpe?g|gif|webp|avif|ico|bmp|woff2?|ttf|eot|otf|md|mdx|txt|html|mp4|webm|mp3|wav|ogg|m4a|pdf|wasm|glsl|frag|vert)$/i
const PROJECT_ROOT = resolve(process.argv[2] ?? process.cwd())
const PAGE_DIR_CANDIDATES = [
'resources/js/Pages',
'resources/js/pages',
'assets/js/Pages',
'assets/js/pages',
'assets/Pages',
'assets/pages',
]

async function loadRolldown() {
const projectRequire = createRequire(join(PROJECT_ROOT, 'package.json'))
const path = projectRequire.resolve('rolldown')
return await import(pathToFileURL(path).href)
}

async function readJsonWithComments(path) {
const raw = await readFile(path, 'utf8')
const stripped = raw
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/(^|[^:])\/\/[^\n]*/g, '$1')
return JSON.parse(stripped)
}

async function loadAliasFromTsconfig() {
const alias = {}
for (const name of ['tsconfig.json', 'jsconfig.json']) {
const p = join(PROJECT_ROOT, name)
if (!existsSync(p)) continue
let cfg
try { cfg = await readJsonWithComments(p) } catch { continue }
const baseUrl = resolve(PROJECT_ROOT, cfg?.compilerOptions?.baseUrl ?? '.')
const paths = cfg?.compilerOptions?.paths ?? {}
for (const [key, targets] of Object.entries(paths)) {
if (!key.endsWith('/*')) continue
const t0 = Array.isArray(targets) ? targets[0] : null
if (typeof t0 !== 'string' || !t0.endsWith('/*')) continue
const aliasKey = key.slice(0, -2)
if (alias[aliasKey] !== undefined) continue
alias[aliasKey] = resolve(baseUrl, t0.slice(0, -2))
}
}
return alias
}

async function listPageFiles(pagesDir) {
if (!existsSync(pagesDir)) return []

const out = []
const walk = async (dir) => {
let entries
try { entries = await readdir(dir, { withFileTypes: true }) } catch { return }
for (const entry of entries) {
const full = resolve(dir, entry.name)
if (entry.isDirectory()) { await walk(full); continue }
if (PAGE_EXTENSIONS.has(extname(entry.name))) out.push(full)
}
}

await walk(pagesDir)
return out
}

async function discoverPagesDir() {
const override = process.env.TIA_VITE_PAGES_DIR
if (override && override.length > 0) {
return resolve(PROJECT_ROOT, override.replace(/\\/g, '/'))
}

for (const rel of PAGE_DIR_CANDIDATES) {
const abs = resolve(PROJECT_ROOT, rel)
if (!existsSync(abs)) continue
const files = await listPageFiles(abs)
if (files.length > 0) return abs
}

return null
}

function componentNameFor(pageAbs, pagesDir) {
const rel = relative(pagesDir, pageAbs).split(sep).join('/')
const ext = extname(rel)
return rel.slice(0, rel.length - ext.length)
}

function isLocalSpecifier(source, aliasKeys) {
if (source.startsWith('.') || source.startsWith('/')) return true
for (const key of aliasKeys) {
if (source === key || source.startsWith(key + '/')) return true
}
return false
}

async function main() {
const pagesDir = await discoverPagesDir()

if (pagesDir === null) {
process.stdout.write('{}')
return
}

const pages = await listPageFiles(pagesDir)

if (pages.length === 0) {
process.stdout.write('{}')
return
}

const { rolldown } = await loadRolldown()
const alias = await loadAliasFromTsconfig()
const aliasKeys = Object.keys(alias)

const graph = new Map()

const collector = {
name: 'pest-tia-collector',
moduleParsed(info) {
const id = info.id
if (!id || id.startsWith('\0')) return
const deps = new Set()
for (const i of info.importedIds) if (i && !i.startsWith('\0')) deps.add(i)
for (const i of info.dynamicallyImportedIds) if (i && !i.startsWith('\0')) deps.add(i)
graph.set(id, deps)
},
}

const externalBare = {
name: 'pest-tia-external-bare',
resolveId(source) {
if (!source) return null
if (isLocalSpecifier(source, aliasKeys)) return null
return { id: source, external: true }
},
}

const assetStub = {
name: 'pest-tia-asset-stub',
load(id) {
if (!id) return null
if (ASSET_EXT_RE.test(id)) {
return { code: 'export default null', moduleSideEffects: false }
}
return null
},
}

const input = Object.create(null)
for (let i = 0; i < pages.length; i++) input[`p${i}`] = pages[i]

const bundle = await rolldown({
input,
cwd: PROJECT_ROOT,
resolve: {
alias,
extensions: ['.tsx', '.ts', '.jsx', '.js', '.mjs', '.cjs', '.json'],
},
transform: { jsx: 'preserve' },
treeshake: false,
plugins: [externalBare, assetStub, collector],
logLevel: 'silent',
onLog: () => {},
})

try {
await bundle.generate({ format: 'esm' })
} finally {
await bundle.close()
}

const reverse = new Map()
const transitiveCache = new Map()

const computeTransitive = (id, stack) => {
const cached = transitiveCache.get(id)
if (cached) return cached
if (stack.has(id)) return null

stack.add(id)
const acc = new Set()
const deps = graph.get(id)
if (deps) {
for (const dep of deps) {
if (!dep || dep.startsWith('\0')) continue
if (dep.startsWith(PROJECT_ROOT)) {
const rel = relative(PROJECT_ROOT, dep).split(sep).join('/')
acc.add(rel)
}
if (stack.has(dep)) continue
const child = computeTransitive(dep, stack)
if (child) for (const r of child) acc.add(r)
}
}
stack.delete(id)
transitiveCache.set(id, acc)
return acc
}

for (const page of pages) {
const pageComponent = componentNameFor(page, pagesDir)
const reachable = computeTransitive(page, new Set())
if (!reachable) continue
for (const rel of reachable) {
const bucket = reverse.get(rel) ?? new Set()
bucket.add(pageComponent)
reverse.set(rel, bucket)
}
}

const payload = Object.create(null)
const keys = [...reverse.keys()].sort()
for (const key of keys) {
payload[key] = [...reverse.get(key)].sort()
}

process.stdout.write(JSON.stringify(payload))
}

try {
void pathToFileURL
await main()
} catch (err) {
process.stderr.write(String(err?.stack ?? err ?? 'unknown error'))
process.exit(1)
}
10 changes: 10 additions & 0 deletions bin/worker.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use ParaTest\WrapperRunner\WrapperWorker;
use Pest\Kernel;
use Pest\Plugins\Actions\CallsHandleArguments;
use Pest\Support\Container;
use Pest\TestSuite;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
Expand Down Expand Up @@ -58,6 +59,15 @@
}
}

$container = Container::getInstance();
$rootPath = dirname(PHPUNIT_COMPOSER_INSTALL, 2);

foreach (Kernel::RESTARTERS as $restarterClass) {
$restarter = $container->get($restarterClass);

$restarter->maybeRestart($rootPath, $_SERVER['argv']);
}

assert(isset($getopt['status-file']) && is_string($getopt['status-file']));
$statusFile = fopen($getopt['status-file'], 'wb');
assert(is_resource($statusFile));
Expand Down
10 changes: 6 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,19 @@
"require": {
"php": "^8.3.0",
"brianium/paratest": "^7.20.0",
"nunomaduro/collision": "^8.9.3",
"composer/xdebug-handler": "^3.0.5",
"nunomaduro/collision": "^8.9.4",
"nunomaduro/termwind": "^2.4.0",
"pestphp/pest-plugin": "^4.0.0",
"pestphp/pest-plugin-arch": "^4.0.2",
"pestphp/pest-plugin-mutate": "^4.0.1",
"pestphp/pest-plugin-profanity": "^4.2.1",
"phpunit/phpunit": "^12.5.23",
"phpunit/phpunit": "^12.5.24",
"symfony/process": "^7.4.8|^8.0.8"
},
"conflict": {
"filp/whoops": "<2.18.3",
"phpunit/phpunit": ">12.5.23",
"phpunit/phpunit": ">12.5.24",
"sebastian/exporter": "<7.0.0",
"webmozart/assert": "<1.11.0"
},
Expand Down Expand Up @@ -58,7 +59,7 @@
]
},
"require-dev": {
"mrpunyapal/peststan": "^0.2.5",
"mrpunyapal/peststan": "^0.2.9",
"pestphp/pest-dev-tools": "^4.1.0",
"pestphp/pest-plugin-browser": "^4.3.1",
"pestphp/pest-plugin-type-coverage": "^4.0.4",
Expand Down Expand Up @@ -123,6 +124,7 @@
"Pest\\Plugins\\Verbose",
"Pest\\Plugins\\Version",
"Pest\\Plugins\\Shard",
"Pest\\Plugins\\Tia",
"Pest\\Plugins\\Parallel"
]
},
Expand Down
2 changes: 2 additions & 0 deletions resources/views/components/badge.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
[$bgBadgeColor, $bgBadgeText] = match ($type) {
'INFO' => ['blue', 'INFO'],
'ERROR' => ['red', 'ERROR'],
'WARN' => ['yellow', 'WARN'],
'SUCCESS' => ['green', 'SUCCESS'],
};

?>
Expand Down
19 changes: 19 additions & 0 deletions src/Bootstrappers/BootPhpUnitConfiguration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Pest\Bootstrappers;

use Pest\Contracts\Bootstrapper;
use PHPUnit\TextUI\Configuration\Builder;

/**
* @internal
*/
final class BootPhpUnitConfiguration implements Bootstrapper
{
public function boot(): void
{
(new Builder)->build(['pest']);
}
}
11 changes: 11 additions & 0 deletions src/Bootstrappers/BootSubscribers.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@
Subscribers\EnsureIgnorableTestCasesAreIgnored::class,
Subscribers\EnsureKernelDumpIsFlushed::class,
Subscribers\EnsureTeamCityEnabled::class,
Subscribers\EnsureTiaIsRunningPestTestsOnly::class,
Subscribers\EnsureTiaStarts::class,
Subscribers\EnsureTiaEnds::class,
Subscribers\EnsureTiaResultsAreCollected::class,
Subscribers\EnsureTiaResultIsRecordedOnPassed::class,
Subscribers\EnsureTiaResultIsRecordedOnFailed::class,
Subscribers\EnsureTiaResultIsRecordedOnErrored::class,
Subscribers\EnsureTiaResultIsRecordedOnSkipped::class,
Subscribers\EnsureTiaResultIsRecordedOnIncomplete::class,
Subscribers\EnsureTiaResultIsRecordedOnRisky::class,
Subscribers\EnsureTiaAssertionsAreRecordedOnFinished::class,
];

/**
Expand Down
Loading
Loading