From 894d8c70620257037c0e9a2574ad9bf77e198cb3 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Wed, 20 May 2026 17:30:50 +0400 Subject: [PATCH] Refactor ComponentStyleClear --- packages/core/src/commands/registry.ts | 2 + .../src/commands/view/ComponentStyleClear.ts | 25 ++++-- .../commands/view/ComponentStyleClear.ts | 76 +++++++++++++++++++ 3 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 packages/core/test/specs/commands/view/ComponentStyleClear.ts diff --git a/packages/core/src/commands/registry.ts b/packages/core/src/commands/registry.ts index 6bb1da11c..418bf3c3c 100644 --- a/packages/core/src/commands/registry.ts +++ b/packages/core/src/commands/registry.ts @@ -14,6 +14,7 @@ import type { ComponentPrevCommandRegistryRun } from './view/ComponentPrev'; import type { ComponentEnterCommandRegistryRun } from './view/ComponentEnter'; import type { ComponentExitCommandRegistryRun } from './view/ComponentExit'; import type { ComponentDeleteCommandRegistryRun } from './view/ComponentDelete'; +import type { ComponentStyleClearCommandRegistryRun } from './view/ComponentStyleClear'; import type { CopyComponentCommandRegistryRun } from './view/CopyComponent'; import type { PasteComponentCommandRegistryRun } from './view/PasteComponent'; import type { CanvasMoveCommandRegistryRun, CanvasMoveCommandRegistryStop } from './view/CanvasMove'; @@ -51,6 +52,7 @@ export interface CommandRegistryRun ComponentEnterCommandRegistryRun, ComponentExitCommandRegistryRun, ComponentDeleteCommandRegistryRun, + ComponentStyleClearCommandRegistryRun, CopyComponentCommandRegistryRun, PasteComponentCommandRegistryRun, CanvasMoveCommandRegistryRun, diff --git a/packages/core/src/commands/view/ComponentStyleClear.ts b/packages/core/src/commands/view/ComponentStyleClear.ts index e867469b4..06bf4a7a3 100644 --- a/packages/core/src/commands/view/ComponentStyleClear.ts +++ b/packages/core/src/commands/view/ComponentStyleClear.ts @@ -1,16 +1,27 @@ import { flatten } from 'underscore'; -import CssRule from '../../css_composer/model/CssRule'; -import { CommandObject } from './CommandAbstract'; +import type CssRule from '../../css_composer/model/CssRule'; +import type Component from '../../dom_components/model/Component'; +import Editor from '../../editor'; +import type { CommandPublicFnFromHandler } from '../registryHelpers'; +import CommandAbstract from './CommandAbstract'; -export default { - run(ed, s, opts = {}) { +export interface ComponentStyleClearRunOptions { + target: Component; +} + +export interface ComponentStyleClearCommandRegistryRun { + 'core:component-style-clear': CommandPublicFnFromHandler; +} + +export default class CommandComponentStyleClear extends CommandAbstract { + run(ed: Editor, s: any, opts: ComponentStyleClearRunOptions) { const { target } = opts; let toRemove: CssRule[] = []; if (!target.get('styles')) return toRemove; // Find all components in the project, of the target component type - const type = target.get('type'); + const type = target.get('type')!; const wrappers = ed.Pages.getAllWrappers(); const len = flatten(wrappers.map((wrp) => wrp.findType(type))).length; @@ -23,5 +34,5 @@ export default { } return toRemove; - }, -} as CommandObject; + } +} diff --git a/packages/core/test/specs/commands/view/ComponentStyleClear.ts b/packages/core/test/specs/commands/view/ComponentStyleClear.ts new file mode 100644 index 000000000..b41985834 --- /dev/null +++ b/packages/core/test/specs/commands/view/ComponentStyleClear.ts @@ -0,0 +1,76 @@ +import ComponentStyleClear from '../../../../src/commands/view/ComponentStyleClear'; + +describe('ComponentStyleClear command', () => { + test('should remove component style rules when no components of that type remain', () => { + const command = new ComponentStyleClear({}); + const ruleA = { get: jest.fn(() => 'cmp:text') }; + const ruleB = { get: jest.fn(() => 'cmp:image') }; + const rules = { + filter: jest.fn((predicate: any) => [ruleA, ruleB].filter(predicate)), + remove: jest.fn(), + }; + const target = { + get: jest.fn((key: string) => { + if (key === 'styles') return true; + if (key === 'type') return 'text'; + }), + }; + const editor = { + Pages: { + getAllWrappers: jest.fn(() => [{ findType: jest.fn(() => []) }]), + }, + CssComposer: { + getAll: jest.fn(() => rules), + }, + }; + + const result = command.run(editor as any, null, { target } as any); + + expect(rules.remove).toHaveBeenCalledWith([ruleA]); + expect(result).toEqual([ruleA]); + }); + + test('should return empty array when target has no styles', () => { + const command = new ComponentStyleClear({}); + const target = { + get: jest.fn((key: string) => (key === 'styles' ? false : 'text')), + }; + const editor = { + Pages: { getAllWrappers: jest.fn() }, + CssComposer: { getAll: jest.fn() }, + }; + + const result = command.run(editor as any, null, { target } as any); + + expect(editor.Pages.getAllWrappers).not.toHaveBeenCalled(); + expect(result).toEqual([]); + }); + + test('should keep rules when components of that type still exist', () => { + const command = new ComponentStyleClear({}); + const rules = { + filter: jest.fn(), + remove: jest.fn(), + }; + const target = { + get: jest.fn((key: string) => { + if (key === 'styles') return true; + if (key === 'type') return 'text'; + }), + }; + const editor = { + Pages: { + getAllWrappers: jest.fn(() => [{ findType: jest.fn(() => [{}]) }]), + }, + CssComposer: { + getAll: jest.fn(() => rules), + }, + }; + + const result = command.run(editor as any, null, { target } as any); + + expect(editor.CssComposer.getAll).not.toHaveBeenCalled(); + expect(rules.remove).not.toHaveBeenCalled(); + expect(result).toEqual([]); + }); +});