fix(extensions): reset stale skill review state on close
This commit is contained in:
parent
2142518461
commit
2201b54f28
4 changed files with 173 additions and 2 deletions
|
|
@ -219,6 +219,18 @@ export const SkillEditorDialog = ({
|
||||||
setMutationError(null);
|
setMutationError(null);
|
||||||
}, [detail, mode, open, projectPath]);
|
}, [detail, mode, open, projectPath]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setReviewPreview(null);
|
||||||
|
setReviewOpen(false);
|
||||||
|
setReviewLoading(false);
|
||||||
|
setSaveLoading(false);
|
||||||
|
setMutationError(null);
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open && mode === 'create' && scope === 'project' && !projectPath) {
|
if (open && mode === 'create' && scope === 'project' && !projectPath) {
|
||||||
setScope('user');
|
setScope('user');
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,18 @@ export const SkillImportDialog = ({
|
||||||
setMutationError(null);
|
setMutationError(null);
|
||||||
}, [open, projectPath]);
|
}, [open, projectPath]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPreview(null);
|
||||||
|
setReviewOpen(false);
|
||||||
|
setReviewLoading(false);
|
||||||
|
setImportLoading(false);
|
||||||
|
setMutationError(null);
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open || folderNameEdited) {
|
if (!open || folderNameEdited) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,8 @@ vi.mock('@renderer/components/extensions/skills/SkillCodeEditor', () => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('@renderer/components/extensions/skills/SkillReviewDialog', () => ({
|
vi.mock('@renderer/components/extensions/skills/SkillReviewDialog', () => ({
|
||||||
SkillReviewDialog: () => null,
|
SkillReviewDialog: ({ open }: { open: boolean }) =>
|
||||||
|
open ? React.createElement('div', { 'data-testid': 'skill-review-dialog' }, 'Review') : null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('lucide-react', () => {
|
vi.mock('lucide-react', () => {
|
||||||
|
|
@ -242,4 +243,74 @@ This file uses a freeform layout without generated sections.
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('clears review state when the editor closes externally', async () => {
|
||||||
|
const host = document.createElement('div');
|
||||||
|
document.body.appendChild(host);
|
||||||
|
const root = createRoot(host);
|
||||||
|
storeState.previewSkillUpsert.mockResolvedValue({
|
||||||
|
planId: 'plan-1',
|
||||||
|
targetSkillDir: '/tmp/project/.claude/skills/new-skill',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
relativePath: 'SKILL.md',
|
||||||
|
absolutePath: '/tmp/project/.claude/skills/new-skill/SKILL.md',
|
||||||
|
action: 'create',
|
||||||
|
oldContent: null,
|
||||||
|
newContent: '# Skill',
|
||||||
|
isBinary: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
warnings: [],
|
||||||
|
summary: { created: 1, updated: 0, deleted: 0, binary: 0 },
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
root.render(
|
||||||
|
React.createElement(SkillEditorDialog, {
|
||||||
|
open: true,
|
||||||
|
mode: 'create',
|
||||||
|
projectPath: '/tmp/project',
|
||||||
|
projectLabel: 'Project',
|
||||||
|
detail: null,
|
||||||
|
onClose: vi.fn(),
|
||||||
|
onSaved: vi.fn(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
const reviewButton = Array.from(host.querySelectorAll('button')).find((button) =>
|
||||||
|
button.textContent?.includes('Review And Create')
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
await act(async () => {
|
||||||
|
reviewButton.click();
|
||||||
|
await Promise.resolve();
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(host.querySelector('[data-testid="skill-review-dialog"]')).not.toBeNull();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
root.render(
|
||||||
|
React.createElement(SkillEditorDialog, {
|
||||||
|
open: false,
|
||||||
|
mode: 'create',
|
||||||
|
projectPath: '/tmp/project',
|
||||||
|
projectLabel: 'Project',
|
||||||
|
detail: null,
|
||||||
|
onClose: vi.fn(),
|
||||||
|
onSaved: vi.fn(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(host.querySelector('[data-testid="skill-review-dialog"]')).toBeNull();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
root.unmount();
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,8 @@ vi.mock('@renderer/components/ui/select', () => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('@renderer/components/extensions/skills/SkillReviewDialog', () => ({
|
vi.mock('@renderer/components/extensions/skills/SkillReviewDialog', () => ({
|
||||||
SkillReviewDialog: () => null,
|
SkillReviewDialog: ({ open }: { open: boolean }) =>
|
||||||
|
open ? React.createElement('div', { 'data-testid': 'skill-review-dialog' }, 'Review') : null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('lucide-react', () => {
|
vi.mock('lucide-react', () => {
|
||||||
|
|
@ -302,4 +303,79 @@ describe('SkillImportDialog', () => {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('clears review state when the import dialog closes externally', async () => {
|
||||||
|
const host = document.createElement('div');
|
||||||
|
document.body.appendChild(host);
|
||||||
|
const root = createRoot(host);
|
||||||
|
|
||||||
|
storeState.previewSkillImport.mockResolvedValue({
|
||||||
|
planId: 'plan-1',
|
||||||
|
targetSkillDir: '/tmp/imported-skill',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
relativePath: 'SKILL.md',
|
||||||
|
absolutePath: '/tmp/imported-skill/SKILL.md',
|
||||||
|
action: 'create',
|
||||||
|
oldContent: null,
|
||||||
|
newContent: '# Skill',
|
||||||
|
isBinary: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
warnings: [],
|
||||||
|
summary: { created: 1, updated: 0, deleted: 0, binary: 0 },
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
root.render(
|
||||||
|
React.createElement(SkillImportDialog, {
|
||||||
|
open: true,
|
||||||
|
projectPath: null,
|
||||||
|
projectLabel: null,
|
||||||
|
onClose: vi.fn(),
|
||||||
|
onImported: vi.fn(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
const sourceInput = host.querySelector('#skill-import-source') as HTMLInputElement;
|
||||||
|
await act(async () => {
|
||||||
|
const setValue = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
|
||||||
|
setValue?.call(sourceInput, '/tmp/source-skill');
|
||||||
|
sourceInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
const reviewButton = Array.from(host.querySelectorAll('button')).find((button) =>
|
||||||
|
button.textContent?.includes('Review And Import')
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
await act(async () => {
|
||||||
|
reviewButton.click();
|
||||||
|
await Promise.resolve();
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(host.querySelector('[data-testid="skill-review-dialog"]')).not.toBeNull();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
root.render(
|
||||||
|
React.createElement(SkillImportDialog, {
|
||||||
|
open: false,
|
||||||
|
projectPath: null,
|
||||||
|
projectLabel: null,
|
||||||
|
onClose: vi.fn(),
|
||||||
|
onImported: vi.fn(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(host.querySelector('[data-testid="skill-review-dialog"]')).toBeNull();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
root.unmount();
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue