File size: 6,484 Bytes
1dbc34b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | /**
* Tests to validate worktree-panel.tsx prop integrity after rebase conflict resolution.
*
* During the rebase onto upstream/v1.0.0rc, duplicate JSX props (isDevServerStarting,
* isStartingAnyDevServer) were introduced by overlapping commits. This test validates
* that the source code has no duplicate JSX prop assignments, which would cause
* React warnings and unpredictable behavior (last value wins).
*/
import { describe, it, expect, beforeAll } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
describe('worktree-panel.tsx prop integrity', () => {
const filePath = path.resolve(
__dirname,
'../../../src/components/views/board-view/worktree-panel/worktree-panel.tsx'
);
let sourceCode: string;
beforeAll(() => {
sourceCode = fs.readFileSync(filePath, 'utf-8');
});
it('should not have duplicate isDevServerStarting props within any single JSX element', () => {
// Parse JSX elements and verify no element has isDevServerStarting more than once.
// Props are passed to WorktreeTab, WorktreeMobileDropdown, WorktreeActionsDropdown, etc.
// Each individual element should have the prop at most once.
const lines = sourceCode.split('\n');
let inElement = false;
let propCount = 0;
let elementName = '';
const violations: string[] = [];
for (let i = 0; i < lines.length; i++) {
const trimmed = lines[i].trimStart();
const elementStart = trimmed.match(/^<(\w+)\b/);
if (elementStart && !trimmed.startsWith('</')) {
inElement = true;
propCount = 0;
elementName = elementStart[1];
}
if (inElement && trimmed.includes('isDevServerStarting=')) {
propCount++;
if (propCount > 1) {
violations.push(`Duplicate isDevServerStarting in <${elementName}> at line ${i + 1}`);
}
}
if (
inElement &&
(trimmed.includes('/>') || (trimmed.endsWith('>') && !trimmed.includes('=')))
) {
inElement = false;
}
}
expect(violations).toEqual([]);
// Verify the prop is actually used somewhere
expect(sourceCode).toContain('isDevServerStarting=');
});
it('should not have duplicate isStartingAnyDevServer props within any single JSX element', () => {
const lines = sourceCode.split('\n');
let inElement = false;
let propCount = 0;
let elementName = '';
const violations: string[] = [];
for (let i = 0; i < lines.length; i++) {
const trimmed = lines[i].trimStart();
const elementStart = trimmed.match(/^<(\w+)\b/);
if (elementStart && !trimmed.startsWith('</')) {
inElement = true;
propCount = 0;
elementName = elementStart[1];
}
if (inElement && trimmed.includes('isStartingAnyDevServer=')) {
propCount++;
if (propCount > 1) {
violations.push(`Duplicate isStartingAnyDevServer in <${elementName}> at line ${i + 1}`);
}
}
if (
inElement &&
(trimmed.includes('/>') || (trimmed.endsWith('>') && !trimmed.includes('=')))
) {
inElement = false;
}
}
expect(violations).toEqual([]);
});
it('should not have any JSX element with duplicate prop names', () => {
// Parse all JSX-like blocks and check for duplicate props
// This regex finds prop assignments like propName={...} or propName="..."
const lines = sourceCode.split('\n');
// Track props per JSX element by looking for indentation patterns
// A JSX opening tag starts with < and ends when indentation drops
let currentJsxProps: Map<string, number[]> = new Map();
let inJsxElement = false;
let _elementIndent = 0;
const duplicates: Array<{ prop: string; line: number; element: string }> = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmed = line.trimStart();
const indent = line.length - trimmed.length;
// Detect start of JSX element
if (trimmed.startsWith('<') && !trimmed.startsWith('</') && !trimmed.startsWith('{')) {
const elementMatch = trimmed.match(/^<(\w+)/);
if (elementMatch) {
inJsxElement = true;
_elementIndent = indent;
currentJsxProps = new Map();
}
}
if (inJsxElement) {
// Extract prop names from this line (prop={value} or prop="value")
const propMatches = trimmed.matchAll(/\b(\w+)=\{/g);
for (const match of propMatches) {
const propName = match[1];
if (!currentJsxProps.has(propName)) {
currentJsxProps.set(propName, []);
}
currentJsxProps.get(propName)!.push(i + 1);
// Check for duplicates
if (currentJsxProps.get(propName)!.length > 1) {
duplicates.push({
prop: propName,
line: i + 1,
element: trimmed.substring(0, 50),
});
}
}
// Detect end of JSX element (self-closing /> or >)
if (trimmed.includes('/>') || (trimmed.endsWith('>') && !trimmed.includes('='))) {
inJsxElement = false;
currentJsxProps = new Map();
}
}
}
expect(duplicates).toEqual([]);
});
});
describe('worktree-panel.tsx uses both isStartingAnyDevServer and isDevServerStarting', () => {
const filePath = path.resolve(
__dirname,
'../../../src/components/views/board-view/worktree-panel/worktree-panel.tsx'
);
let sourceCode: string;
beforeAll(() => {
sourceCode = fs.readFileSync(filePath, 'utf-8');
});
it('should use isStartingAnyDevServer from the useDevServers hook', () => {
// The hook destructuring should include isStartingAnyDevServer
expect(sourceCode).toContain('isStartingAnyDevServer');
});
it('should use isDevServerStarting from the useDevServers hook', () => {
// The hook destructuring should include isDevServerStarting
expect(sourceCode).toContain('isDevServerStarting');
});
it('isStartingAnyDevServer and isDevServerStarting should be distinct concepts', () => {
// isStartingAnyDevServer is a boolean (any server starting)
// isDevServerStarting is a function (specific worktree starting)
// Both should be destructured from the hook
const hookDestructuring = sourceCode.match(
/const\s*\{[^}]*isStartingAnyDevServer[^}]*isDevServerStarting[^}]*\}/s
);
expect(hookDestructuring).not.toBeNull();
});
});
|