Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .phpunit.result.cache

Large diffs are not rendered by default.

16 changes: 2 additions & 14 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,7 @@ <h3 class="text-lg font-semibold text-white">Filters & Settings</h3>
</select>
</div>

<div class="form-group">
<label class="block text-sm font-medium text-gray-300 mb-2">Result Update Mode</label>
<div class="space-y-2">
<label class="flex items-center text-sm">
<input type="radio" v-model="store.state.options.resultUpdateMode" value="update" class="mr-2">
Update (append/merge new test results)
</label>
<label class="flex items-center text-sm">
<input type="radio" v-model="store.state.options.resultUpdateMode" value="reset" class="mr-2">
Reset (clear all results on each run)
</label>
</div>
</div>


<div class="form-group">
<label class="block text-sm font-medium text-gray-300 mb-2">Display Mode</label>
Expand Down Expand Up @@ -652,4 +640,4 @@ <h3 class="text-lg font-semibold text-white mb-2">{{ store.state.fileCoverage.pa
}).mount('#app');
</script>
</body>
</html>
</html>
149 changes: 8 additions & 141 deletions public/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,8 @@ export class App {
this.store.setStarting(true);
this.store.state.activeTab = 'results';

// In update mode, clear results for "Run All" or "Run Failed"
if (this.store.state.options.resultUpdateMode === 'update') {
if (runOptions.contextId === 'global' || runOptions.contextId === 'failed') {
this.store.clearAllResults();
}
}

// Filter out frontend-only options that PHPUnit doesn't understand
const { resultUpdateMode, displayMode, ...phpunitOptions } = this.store.state.options;
const { displayMode, ...phpunitOptions } = this.store.state.options;

const payload = {
filters: runOptions.filters || [],
Expand Down Expand Up @@ -267,7 +260,7 @@ export class App {
}

/**
* Get results from current test run (or merged results in update mode)
* Get results from current test run (always reset mode)
*/
getResults() {
const runs = this.store.state.realtimeTestRuns;
Expand All @@ -277,19 +270,12 @@ export class App {
return null;
}

// In update mode, merge all completed/finished runs
const isUpdateMode = this.store.state.options.resultUpdateMode === 'update';

if (isUpdateMode) {
return this.getMergedResults(runs);
} else {
// In reset mode, show only the last completed run
let runId = this.store.state.lastCompletedRunId;
if (!runId) {
runId = runIds[runIds.length - 1];
}
return this.getSingleRunResults(runs[runId]);
// Always show only the last completed run
let runId = this.store.state.lastCompletedRunId;
if (!runId) {
runId = runIds[runIds.length - 1];
}
return this.getSingleRunResults(runs[runId]);
}

/**
Expand Down Expand Up @@ -353,125 +339,6 @@ export class App {
};
}

/**
* Get merged results from all test runs
*/
getMergedResults(runs) {
const mergedSuites = {};
const mergedSummary = {
numberOfTests: 0,
numberOfAssertions: 0,
duration: 0,
numberOfFailures: 0,
numberOfErrors: 0,
numberOfWarnings: 0,
numberOfSkipped: 0,
numberOfDeprecations: 0,
numberOfIncomplete: 0,
};

// Track which tests have been seen in summaries to avoid double-counting assertions
const testsSeen = new Set();

// Merge all runs
for (const runId in runs) {
const run = runs[runId];

// Only merge runs that have completed (or are running with data)
// Skip runs that are just initialized but have no test data yet
if (Object.keys(run.suites).length === 0) {
continue;
}

// Merge suites and tests
for (const suiteName in run.suites) {
if (!mergedSuites[suiteName]) {
mergedSuites[suiteName] = {
name: suiteName,
tests: {},
runIds: []
};
}
const suiteData = run.suites[suiteName];
for (const testId in suiteData.tests) {
// Later runs override earlier runs for the same test
mergedSuites[suiteName].tests[testId] = {
...suiteData.tests[testId],
runId: runId
};

// Mark this test as seen in this run
if (!testsSeen.has(testId)) {
testsSeen.add(testId);
}
}

// Track which runs contributed to this suite
if (!mergedSuites[suiteName].runIds.includes(runId)) {
mergedSuites[suiteName].runIds.push(runId);
}
}

// Accumulate summaries from all completed runs
// For assertions and duration, we need to accumulate from each unique run
// For other stats, we'll recalculate from merged tests
if (run.summary && run.status === 'finished') {
mergedSummary.duration += run.summary.duration || 0;
}
}

// Transform merged suites
const transformedSuites = [];
for (const suiteName in mergedSuites) {
const suiteData = mergedSuites[suiteName];
const testcases = [];
for (const testId in suiteData.tests) {
const testData = suiteData.tests[testId];
testcases.push({
name: testData.name,
class: testData.class,
id: testData.id,
duration: testData.duration || 0,
assertions: testData.assertions || 0,
status: testData.status,
message: testData.message,
trace: testData.trace,
warnings: testData.warnings || [],
deprecations: testData.deprecations || [],
});
}
if (testcases.length > 0) {
transformedSuites.push({
name: suiteData.name,
testcases,
});
}
}

// If no tests found, return null
if (transformedSuites.length === 0) {
return null;
}

// Calculate counts from merged tests (but keep accumulated assertions and duration)
const calculatedSummary = this.calculateSummaryFromTests(transformedSuites);

return {
summary: {
tests: calculatedSummary.tests,
assertions: calculatedSummary.assertions,
time: calculatedSummary.time,
failures: calculatedSummary.failures,
errors: calculatedSummary.errors,
warnings: calculatedSummary.warnings,
skipped: calculatedSummary.skipped,
deprecations: calculatedSummary.deprecations,
incomplete: calculatedSummary.incomplete,
},
suites: transformedSuites,
};
}

/**
* Calculate summary statistics from test data
*/
Expand Down Expand Up @@ -719,4 +586,4 @@ export class App {
console.error('Failed to fetch file coverage:', error);
}
}
}
}
57 changes: 11 additions & 46 deletions public/js/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export class Store {
stopOnError: false,
stopOnFailure: false,
stopOnWarning: false,
resultUpdateMode: 'update', // 'update' (append/merge) or 'reset' (clear all)
displayMode: 'default', // 'default' or 'individual'
};

Expand Down Expand Up @@ -70,7 +69,9 @@ export class Store {
try {
const parsedState = JSON.parse(savedState);
if (parsedState.options) {
this.state.options = { ...this.state.options, ...parsedState.options };
// Ensure resultUpdateMode is not loaded from old state
const { resultUpdateMode, ...restOptions } = parsedState.options;
this.state.options = { ...this.state.options, ...restOptions };
}
if (Array.isArray(parsedState.selectedSuites)) {
this.state.selectedSuites = parsedState.selectedSuites;
Expand Down Expand Up @@ -128,10 +129,8 @@ export class Store {
*/
initializeTestRun(runId, contextId) {
this.state.isStarting = false;
// Determine if we should reset results
// Always reset for 'global' runs in reset mode, or for 'failed' runs (to show only re-run tests)
const shouldReset = (this.state.options.resultUpdateMode === 'reset' && contextId === 'global') ||
contextId === 'failed';
// Always reset for 'global' runs or for 'failed' runs (to show only re-run tests)
const shouldReset = contextId === 'global' || contextId === 'failed';

this.state.realtimeTestRuns[runId] = {
status: 'running',
Expand Down Expand Up @@ -312,15 +311,6 @@ export class Store {
} else if (status === 'passed') {
// Remove from current run
run.failedTestIds.delete(testId);

// In update mode, also remove from all other runs (test now passes)
if (this.state.options.resultUpdateMode === 'update') {
for (const otherRunId in this.state.realtimeTestRuns) {
if (otherRunId !== runId) {
this.state.realtimeTestRuns[otherRunId].failedTestIds?.delete(testId);
}
}
}
} else if (status !== 'passed') {
suite.hasIssues = true;
}
Expand Down Expand Up @@ -358,8 +348,8 @@ export class Store {
this.state.lastCompletedRunId = runId;
delete this.state.runningTestIds[runId];
delete this.state.stopPending[runId];
this.updateSidebarAfterRun(runId);
this.state.isStarting = false;
this.updateSidebarAfterRun(runId);
updateFavicon(run.summary.status === 'passed' ? 'success' : 'failure');
}

Expand Down Expand Up @@ -495,44 +485,19 @@ export class Store {
}

/**
* Get failed test IDs from all runs (in update mode) or last run (in reset mode)
* Get failed test IDs from last run
*/
getFailedTestIds() {
if (this.state.options.resultUpdateMode === 'update') {
// In update mode, collect failed tests from all runs
const allFailedTests = new Set();
for (const runId in this.state.realtimeTestRuns) {
const run = this.state.realtimeTestRuns[runId];
if (run.failedTestIds) {
run.failedTestIds.forEach(testId => allFailedTests.add(testId));
}
}
return Array.from(allFailedTests);
} else {
// In reset mode, only show failed tests from last completed run
const run = this.state.realtimeTestRuns[this.state.lastCompletedRunId];
return run ? Array.from(run.failedTestIds) : [];
}
const run = this.state.realtimeTestRuns[this.state.lastCompletedRunId];
return run ? Array.from(run.failedTestIds) : [];
}

/**
* Check if there are failed tests
*/
hasFailedTests() {
if (this.state.options.resultUpdateMode === 'update') {
// In update mode, check all runs for failed tests
for (const runId in this.state.realtimeTestRuns) {
const run = this.state.realtimeTestRuns[runId];
if (run.failedTestIds && run.failedTestIds.size > 0) {
return true;
}
}
return false;
} else {
// In reset mode, only check last completed run
const run = this.state.realtimeTestRuns[this.state.lastCompletedRunId];
return run ? run.failedTestIds.size > 0 : false;
}
const run = this.state.realtimeTestRuns[this.state.lastCompletedRunId];
return run ? run.failedTestIds.size > 0 : false;
}

/**
Expand Down
46 changes: 0 additions & 46 deletions public/js/tests/store.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,6 @@ describe('Store', () => {
expect(store.state.realtimeTestRuns['oldRun']).toBeUndefined();
expect(store.state.lastCompletedRunId).toBeNull();
});

test('should not reset results for global context in update mode', () => {
const runId = 'run123';
store.state.options.resultUpdateMode = 'update';
store.state.realtimeTestRuns['oldRun'] = { status: 'finished' };
store.state.lastCompletedRunId = 'oldRun';

store.initializeTestRun(runId, 'global');
expect(store.state.realtimeTestRuns['oldRun']).toBeDefined();
expect(store.state.lastCompletedRunId).toBe('oldRun');
});
});

describe('handleTestEvent', () => {
Expand Down Expand Up @@ -343,21 +332,6 @@ describe('Store', () => {
store.handleTestCompleted(run, eventData, runId);
expect(run.failedTestIds.has(testId)).toBe(false);
});

test('should remove test from all runs failedTestIds in update mode when test passes', () => {
store.state.options.resultUpdateMode = 'update';
const runId2 = 'run456';
store.initializeTestRun(runId2, 'global');
const run2 = store.state.realtimeTestRuns[runId2];
run2.failedTestIds.add(testId);
run.failedTestIds.add(testId);

const eventData = { event: 'test.passed', data: { test: testId } };
store.handleTestCompleted(run, eventData, runId);

expect(run.failedTestIds.has(testId)).toBe(false);
expect(run2.failedTestIds.has(testId)).toBe(false);
});
});

describe('handleTestFinished', () => {
Expand Down Expand Up @@ -445,18 +419,6 @@ describe('Store', () => {

expect(store.getFailedTestIds()).toEqual(['test1', 'test2']);
});

test('should return failed test IDs from all runs in update mode', () => {
store.state.options.resultUpdateMode = 'update';
store.initializeTestRun('run1', 'global');
store.state.realtimeTestRuns['run1'].failedTestIds.add('test1');
store.initializeTestRun('run2', 'global');
store.state.realtimeTestRuns['run2'].failedTestIds.add('test2');

const failedIds = store.getFailedTestIds();
expect(failedIds).toContain('test1');
expect(failedIds).toContain('test2');
});
});

describe('hasFailedTests', () => {
Expand All @@ -469,14 +431,6 @@ describe('Store', () => {

expect(store.hasFailedTests()).toBe(true);
});

test('should return true if any run has failed tests in update mode', () => {
store.state.options.resultUpdateMode = 'update';
store.initializeTestRun('run1', 'global');
store.state.realtimeTestRuns['run1'].failedTestIds.add('test1');

expect(store.hasFailedTests()).toBe(true);
});
});

describe('clearAllResults', () => {
Expand Down
Binary file modified screenshots/filters_settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading