mirror of https://github.com/artf/grapesjs.git
Browse Source
* Initial setup for typed commands * Remove usage of require * Update tests * Update tests * Move TS checks down * Refactor Preview * Refactor resize command * Refactor CommandCopyComponent * Refactor PasteComponent * Refactor CommandCanvasMove * Refactor CommandCanvasClear * Refactor ExportTemplate * Refactor OpenLayers * Refactor CommandOpenStyleManager * Refactor OpenTraitManager * Refactor OpenBlocks * Refactor OpenAssets * Refactor SwitchVisibility * Refactor ShowOffset * Refactor MoveComponent * Refactor SelectComponent * Refactor ComponentNext * Refactor ComponentPrev * Refactor ComponentEnter * Refactor ComponentExit * Refactor ComponentDelete * Refactor ComponentStyleClear * Refactor ComponentDrag * Refactor SelectPosition * Add remove command method to the CommandManager class * Formatrelease-v0.23.1-rc.0
committed by
GitHub
59 changed files with 2368 additions and 947 deletions
@ -0,0 +1,130 @@ |
|||
import type { ObjectAny } from '../common'; |
|||
import type { CommandPublicOptions } from './registryHelpers'; |
|||
import type CommandAbstract from './view/CommandAbstract'; |
|||
import type { CommandConstructor, CommandFunction, CommandObject } from './view/CommandAbstract'; |
|||
import type { FullscreenCommandRegistryRun, FullscreenCommandRegistryStop } from './view/Fullscreen'; |
|||
import type { PreviewCommandRegistryRun, PreviewCommandRegistryStop } from './view/Preview'; |
|||
import type { ResizeCommandRegistryRun, ResizeCommandRegistryStop } from './view/Resize'; |
|||
import type { ComponentNextCommandRegistryRun } from './view/ComponentNext'; |
|||
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 { ComponentDragCommandRegistryRun } from './view/ComponentDrag'; |
|||
import type { CopyComponentCommandRegistryRun } from './view/CopyComponent'; |
|||
import type { PasteComponentCommandRegistryRun } from './view/PasteComponent'; |
|||
import type { CanvasMoveCommandRegistryRun, CanvasMoveCommandRegistryStop } from './view/CanvasMove'; |
|||
import type { CanvasClearCommandRegistryRun } from './view/CanvasClear'; |
|||
import type { ExportTemplateCommandRegistryRun, ExportTemplateCommandRegistryStop } from './view/ExportTemplate'; |
|||
import type { OpenAssetsCommandRegistryRun, OpenAssetsCommandRegistryStop } from './view/OpenAssets'; |
|||
import type { OpenLayersCommandRegistryRun, OpenLayersCommandRegistryStop } from './view/OpenLayers'; |
|||
import type { OpenBlocksCommandRegistryRun, OpenBlocksCommandRegistryStop } from './view/OpenBlocks'; |
|||
import type { MoveComponentCommandRegistryRun, MoveComponentCommandRegistryStop } from './view/MoveComponent'; |
|||
import type { SelectComponentCommandRegistryRun, SelectComponentCommandRegistryStop } from './view/SelectComponent'; |
|||
import type { ShowOffsetCommandRegistryRun, ShowOffsetCommandRegistryStop } from './view/ShowOffset'; |
|||
import type { SwitchVisibilityCommandRegistryRun, SwitchVisibilityCommandRegistryStop } from './view/SwitchVisibility'; |
|||
import type { OpenStyleManagerCommandRegistryRun, OpenStyleManagerCommandRegistryStop } from './view/OpenStyleManager'; |
|||
import type { OpenTraitManagerCommandRegistryRun, OpenTraitManagerCommandRegistryStop } from './view/OpenTraitManager'; |
|||
|
|||
type CommandRegistryHandler = (...args: any[]) => any; |
|||
type CommandRegistryEntry<TRegistry, TId extends string> = TId extends keyof TRegistry |
|||
? TRegistry[TId] extends CommandRegistryHandler |
|||
? TRegistry[TId] |
|||
: CommandRegistryHandler |
|||
: CommandRegistryHandler; |
|||
|
|||
export interface CommandRegistryRun |
|||
extends FullscreenCommandRegistryRun, |
|||
PreviewCommandRegistryRun, |
|||
ResizeCommandRegistryRun, |
|||
ComponentNextCommandRegistryRun, |
|||
ComponentPrevCommandRegistryRun, |
|||
ComponentEnterCommandRegistryRun, |
|||
ComponentExitCommandRegistryRun, |
|||
ComponentDeleteCommandRegistryRun, |
|||
ComponentStyleClearCommandRegistryRun, |
|||
ComponentDragCommandRegistryRun, |
|||
CopyComponentCommandRegistryRun, |
|||
PasteComponentCommandRegistryRun, |
|||
CanvasMoveCommandRegistryRun, |
|||
CanvasClearCommandRegistryRun, |
|||
ExportTemplateCommandRegistryRun, |
|||
MoveComponentCommandRegistryRun, |
|||
OpenAssetsCommandRegistryRun, |
|||
OpenBlocksCommandRegistryRun, |
|||
OpenLayersCommandRegistryRun, |
|||
OpenStyleManagerCommandRegistryRun, |
|||
OpenTraitManagerCommandRegistryRun, |
|||
SelectComponentCommandRegistryRun, |
|||
ShowOffsetCommandRegistryRun, |
|||
SwitchVisibilityCommandRegistryRun {} |
|||
|
|||
export interface CommandRegistryStop |
|||
extends FullscreenCommandRegistryStop, |
|||
PreviewCommandRegistryStop, |
|||
ResizeCommandRegistryStop, |
|||
CanvasMoveCommandRegistryStop, |
|||
ExportTemplateCommandRegistryStop, |
|||
MoveComponentCommandRegistryStop, |
|||
OpenAssetsCommandRegistryStop, |
|||
OpenBlocksCommandRegistryStop, |
|||
OpenLayersCommandRegistryStop, |
|||
OpenStyleManagerCommandRegistryStop, |
|||
OpenTraitManagerCommandRegistryStop, |
|||
SelectComponentCommandRegistryStop, |
|||
ShowOffsetCommandRegistryStop, |
|||
SwitchVisibilityCommandRegistryStop {} |
|||
|
|||
export type CommandRunKnownId = Extract<keyof CommandRegistryRun, string>; |
|||
export type CommandStopKnownId = Extract<keyof CommandRegistryStop, string>; |
|||
export type CommandKnownId = Extract<keyof CommandRegistryRun | keyof CommandRegistryStop, string>; |
|||
|
|||
export type CommandRunPublicFn<TId extends string> = CommandRegistryEntry<CommandRegistryRun, TId>; |
|||
export type CommandStopPublicFn<TId extends string> = CommandRegistryEntry<CommandRegistryStop, TId>; |
|||
|
|||
export type CommandRunArgs<TId extends string> = TId extends keyof CommandRegistryRun |
|||
? Parameters<CommandRunPublicFn<TId>> |
|||
: [options?: any]; |
|||
export type CommandStopArgs<TId extends string> = TId extends keyof CommandRegistryStop |
|||
? Parameters<CommandStopPublicFn<TId>> |
|||
: [options?: any]; |
|||
|
|||
export type CommandRunOptions<TId extends string> = TId extends keyof CommandRegistryRun |
|||
? CommandPublicOptions<CommandRunPublicFn<TId>> |
|||
: any; |
|||
export type CommandStopOptions<TId extends string> = TId extends keyof CommandRegistryStop |
|||
? CommandPublicOptions<CommandStopPublicFn<TId>> |
|||
: any; |
|||
|
|||
export type CommandRunResult<TId extends string> = TId extends keyof CommandRegistryRun |
|||
? ReturnType<CommandRunPublicFn<TId>> |
|||
: any; |
|||
export type CommandStopResult<TId extends string> = TId extends keyof CommandRegistryStop |
|||
? ReturnType<CommandStopPublicFn<TId>> |
|||
: any; |
|||
|
|||
export type CommandFunctionById<TId extends string> = CommandFunction<CommandRunOptions<TId>, CommandRunResult<TId>>; |
|||
export type CommandObjectById<TId extends string, T extends ObjectAny = {}> = CommandObject< |
|||
CommandRunOptions<TId>, |
|||
T, |
|||
CommandStopOptions<TId>, |
|||
CommandRunResult<TId>, |
|||
CommandStopResult<TId> |
|||
>; |
|||
export type CommandConstructorById<TId extends string> = CommandConstructor< |
|||
CommandRunOptions<TId>, |
|||
CommandStopOptions<TId>, |
|||
CommandRunResult<TId>, |
|||
CommandStopResult<TId> |
|||
>; |
|||
export type CommandDefinitionById<TId extends string, T extends ObjectAny = {}> = |
|||
| CommandFunctionById<TId> |
|||
| CommandObjectById<TId, T> |
|||
| CommandConstructorById<TId>; |
|||
export type CommandInstanceById<TId extends string> = CommandAbstract< |
|||
CommandRunOptions<TId>, |
|||
CommandStopOptions<TId>, |
|||
CommandRunResult<TId>, |
|||
CommandStopResult<TId> |
|||
>; |
|||
@ -0,0 +1,8 @@ |
|||
type CommandPublicHandler = (...args: any[]) => any; |
|||
|
|||
export type CommandPublicFnFromHandler<T> = T extends (editor: any, sender: any, ...args: infer P) => infer R |
|||
? (...args: P) => R |
|||
: never; |
|||
|
|||
export type CommandPublicOptions<T extends CommandPublicHandler> = |
|||
Parameters<T> extends [] ? undefined : Parameters<T>[0]; |
|||
@ -1,8 +1,13 @@ |
|||
import { CommandObject } from './CommandAbstract'; |
|||
import type { CommandPublicFnFromHandler } from '../registryHelpers'; |
|||
import CommandAbstract from './CommandAbstract'; |
|||
|
|||
export default { |
|||
run(ed) { |
|||
export interface CanvasClearCommandRegistryRun { |
|||
'core:canvas-clear': CommandPublicFnFromHandler<CommandCanvasClear['run']>; |
|||
} |
|||
|
|||
export default class CommandCanvasClear extends CommandAbstract { |
|||
run(ed: any) { |
|||
ed.Components.clear(); |
|||
ed.Css.clear(); |
|||
}, |
|||
} as CommandObject; |
|||
} |
|||
} |
|||
|
|||
@ -1,17 +1,23 @@ |
|||
import Component from '../../dom_components/model/Component'; |
|||
import { CommandObject } from './CommandAbstract'; |
|||
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) { |
|||
export interface ComponentEnterCommandRegistryRun { |
|||
'core:component-enter': CommandPublicFnFromHandler<CommandComponentEnter['run']>; |
|||
} |
|||
|
|||
export default class CommandComponentEnter extends CommandAbstract { |
|||
run(ed: Editor) { |
|||
if (!ed.Canvas.hasFocus()) return; |
|||
const toSelect: Component[] = []; |
|||
|
|||
ed.getSelectedAll().forEach((component) => { |
|||
const coll = component.components(); |
|||
const next = coll && coll.filter((c: any) => c.get('selectable'))[0]; |
|||
const next = coll && coll.filter((c) => !!c.get('selectable'))[0]; |
|||
next && toSelect.push(next); |
|||
}); |
|||
|
|||
toSelect.length && ed.select(toSelect); |
|||
}, |
|||
} as CommandObject; |
|||
} |
|||
} |
|||
|
|||
@ -1,9 +1,15 @@ |
|||
import { CommandObject } from './CommandAbstract'; |
|||
import Editor from '../../editor'; |
|||
import type { CommandPublicFnFromHandler } from '../registryHelpers'; |
|||
import CommandAbstract from './CommandAbstract'; |
|||
|
|||
export default { |
|||
run(ed) { |
|||
export interface CopyComponentCommandRegistryRun { |
|||
'core:copy': CommandPublicFnFromHandler<CommandCopyComponent['run']>; |
|||
} |
|||
|
|||
export default class CommandCopyComponent extends CommandAbstract { |
|||
run(ed: Editor) { |
|||
const em = ed.getModel(); |
|||
const models = [...ed.getSelectedAll()].map((md) => md.delegate?.copy?.(md) || md).filter(Boolean); |
|||
models.length && em.set('clipboard', models); |
|||
}, |
|||
} as CommandObject; |
|||
} |
|||
} |
|||
|
|||
@ -1,71 +1,82 @@ |
|||
import { CommandObject } from './CommandAbstract'; |
|||
import { $ } from '../../common'; |
|||
import { ComponentsEvents } from '../../dom_components/types'; |
|||
import Editor from '../../editor'; |
|||
import type { CommandPublicFnFromHandler } from '../registryHelpers'; |
|||
import CommandAbstract from './CommandAbstract'; |
|||
|
|||
export default { |
|||
run(editor, sender) { |
|||
export interface OpenTraitManagerCommandRegistryRun { |
|||
'core:open-traits': CommandPublicFnFromHandler<CommandOpenTraitManager['run']>; |
|||
'open-tm': CommandPublicFnFromHandler<CommandOpenTraitManager['run']>; |
|||
} |
|||
|
|||
export interface OpenTraitManagerCommandRegistryStop { |
|||
'core:open-traits': CommandPublicFnFromHandler<CommandOpenTraitManager['stop']>; |
|||
'open-tm': CommandPublicFnFromHandler<CommandOpenTraitManager['stop']>; |
|||
} |
|||
|
|||
export default class CommandOpenTraitManager extends CommandAbstract { |
|||
sender?: any; |
|||
target?: any; |
|||
$cn?: any; |
|||
$cn2?: any; |
|||
$header?: any; |
|||
|
|||
run(editor: Editor, sender: any) { |
|||
this.sender = sender; |
|||
const em = editor.getModel(); |
|||
|
|||
const config = editor.Config; |
|||
const pfx = config.stylePrefix; |
|||
const tm = editor.TraitManager; |
|||
const confTm = tm.getConfig(); |
|||
let panelC; |
|||
|
|||
if (confTm.appendTo) return; |
|||
|
|||
if (!this.$cn) { |
|||
this.$cn = $('<div></div>'); |
|||
this.$cn2 = $('<div></div>'); |
|||
this.$cn.append(this.$cn2); |
|||
const $cn = $('<div></div>'); |
|||
const $cn2 = $('<div></div>'); |
|||
this.$cn = $cn; |
|||
this.$cn2 = $cn2; |
|||
$cn.append($cn2); |
|||
this.$header = $('<div>').append(`<div class="${confTm.stylePrefix}header">${em.t('traitManager.empty')}</div>`); |
|||
this.$cn.append(this.$header); |
|||
$cn.append(this.$header); |
|||
|
|||
if (confTm.custom) { |
|||
tm.__trgCustom({ container: this.$cn2.get(0) }); |
|||
} else { |
|||
this.$cn2.append(`<div class="${pfx}traits-label">${em.t('traitManager.label')}</div>`); |
|||
this.$cn2.append(tm.render()); |
|||
} |
|||
|
|||
var panels = editor.Panels; |
|||
|
|||
if (!panels.getPanel('views-container')) { |
|||
// @ts-ignore
|
|||
panelC = panels.addPanel({ id: 'views-container' }); |
|||
tm.__trgCustom({ container: $cn2.get(0) }); |
|||
} else { |
|||
panelC = panels.getPanel('views-container'); |
|||
$cn2.append(`<div class="${pfx}traits-label">${em.t('traitManager.label')}</div>`); |
|||
$cn2.append(tm.render()); |
|||
} |
|||
|
|||
panelC?.set('appendContent', this.$cn.get(0)).trigger('change:appendContent'); |
|||
const panels = editor.Panels; |
|||
const panel = panels.getPanel('views-container') || panels.addPanel({ id: 'views-container' }); |
|||
panel?.set('appendContent', $cn.get(0)).trigger('change:appendContent'); |
|||
|
|||
this.target = editor.getModel(); |
|||
this.target = em; |
|||
this.listenTo(this.target, ComponentsEvents.toggled, this.toggleTm); |
|||
} |
|||
|
|||
this.toggleTm(); |
|||
}, |
|||
} |
|||
|
|||
/** |
|||
* Toggle Trait Manager visibility |
|||
* @private |
|||
*/ |
|||
toggleTm() { |
|||
const sender = this.sender; |
|||
if (sender && sender.get && !sender.get('active')) return; |
|||
const { sender, target, $cn2, $header } = this; |
|||
if ((sender && sender.get && !sender.get('active')) || !target) return; |
|||
|
|||
if (this.target.getSelectedAll().length === 1) { |
|||
this.$cn2.show(); |
|||
this.$header.hide(); |
|||
if (target.getSelectedAll().length === 1) { |
|||
$cn2?.show(); |
|||
$header?.hide(); |
|||
} else { |
|||
this.$cn2.hide(); |
|||
this.$header.show(); |
|||
$cn2?.hide(); |
|||
$header?.show(); |
|||
} |
|||
}, |
|||
} |
|||
|
|||
stop() { |
|||
this.$cn2 && this.$cn2.hide(); |
|||
this.$header && this.$header.hide(); |
|||
}, |
|||
} as CommandObject<{}, { [k: string]: any }>; |
|||
this.$cn2?.hide(); |
|||
this.$header?.hide(); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,16 @@ |
|||
import CanvasClear from '../../../../src/commands/view/CanvasClear'; |
|||
|
|||
describe('CanvasClear command', () => { |
|||
test('should clear components and css', () => { |
|||
const command = new CanvasClear({}); |
|||
const editor = { |
|||
Components: { clear: jest.fn() }, |
|||
Css: { clear: jest.fn() }, |
|||
}; |
|||
|
|||
command.run(editor); |
|||
|
|||
expect(editor.Components.clear).toHaveBeenCalledTimes(1); |
|||
expect(editor.Css.clear).toHaveBeenCalledTimes(1); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,24 @@ |
|||
import CanvasMove from '../../../../src/commands/view/CanvasMove'; |
|||
|
|||
describe('CanvasMove command', () => { |
|||
test('stop should toggle move off and disable the dragger', () => { |
|||
const command = new CanvasMove({}); |
|||
command.toggleMove = jest.fn(); |
|||
command.disableDragger = jest.fn(); |
|||
|
|||
command.stop(); |
|||
|
|||
expect(command.toggleMove).toHaveBeenCalledWith(); |
|||
expect(command.disableDragger).toHaveBeenCalledWith(expect.any(MouseEvent)); |
|||
}); |
|||
|
|||
test('onKeyUp should stop the command on space key', () => { |
|||
const command = new CanvasMove({}); |
|||
command.editor = { stopCommand: jest.fn() } as any; |
|||
command.id = 'core:canvas-move'; |
|||
|
|||
command.onKeyUp({ which: 32 } as KeyboardEvent); |
|||
|
|||
expect(command.editor.stopCommand).toHaveBeenCalledWith('core:canvas-move'); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,58 @@ |
|||
import ComponentDelete from '../../../../src/commands/view/ComponentDelete'; |
|||
|
|||
describe('ComponentDelete command', () => { |
|||
test('should remove selected removable components', () => { |
|||
const command = new ComponentDelete({ em: { logWarning: jest.fn() } }); |
|||
const componentA = { get: jest.fn(() => true), remove: jest.fn() }; |
|||
const componentB = { get: jest.fn(() => true), remove: jest.fn() }; |
|||
const editor = { |
|||
getSelectedAll: jest.fn(() => [componentA, componentB]), |
|||
selectRemove: jest.fn(), |
|||
}; |
|||
|
|||
const result = command.run(editor as any, null, {}); |
|||
|
|||
expect(componentA.remove).toHaveBeenCalledTimes(1); |
|||
expect(componentB.remove).toHaveBeenCalledTimes(1); |
|||
expect(editor.selectRemove).toHaveBeenCalledWith([componentA, componentB]); |
|||
expect(result).toEqual([componentA, componentB]); |
|||
}); |
|||
|
|||
test('should use delegated remove target when available', () => { |
|||
const command = new ComponentDelete({ em: { logWarning: jest.fn() } }); |
|||
const delegated = { remove: jest.fn() }; |
|||
const component = { |
|||
get: jest.fn(() => true), |
|||
delegate: { |
|||
remove: jest.fn(() => delegated), |
|||
}, |
|||
}; |
|||
const editor = { |
|||
getSelectedAll: jest.fn(() => [component]), |
|||
selectRemove: jest.fn(), |
|||
}; |
|||
|
|||
command.run(editor as any, null, {}); |
|||
|
|||
expect(component.delegate.remove).toHaveBeenCalledWith(component); |
|||
expect(delegated.remove).toHaveBeenCalledTimes(1); |
|||
expect(editor.selectRemove).toHaveBeenCalledWith([component]); |
|||
}); |
|||
|
|||
test('should warn and skip non-removable components', () => { |
|||
const logWarning = jest.fn(); |
|||
const command = new ComponentDelete({ em: { logWarning } }); |
|||
const component = { get: jest.fn(() => false), remove: jest.fn() }; |
|||
const editor = { |
|||
getSelectedAll: jest.fn(() => [component]), |
|||
selectRemove: jest.fn(), |
|||
}; |
|||
|
|||
const result = command.run(editor as any, null, {}); |
|||
|
|||
expect(logWarning).toHaveBeenCalledWith('The element is not removable', { component }); |
|||
expect(component.remove).not.toHaveBeenCalled(); |
|||
expect(editor.selectRemove).toHaveBeenCalledWith([]); |
|||
expect(result).toEqual([]); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,23 @@ |
|||
import ComponentDrag from '../../../../src/commands/view/ComponentDrag'; |
|||
|
|||
describe('ComponentDrag command', () => { |
|||
test('getTranslate should extract axis values from transform', () => { |
|||
const command = new ComponentDrag({}); |
|||
|
|||
expect(command.getTranslate('translateX(10px) translateY(20px)')).toBe(10); |
|||
expect(command.getTranslate('translateX(10px) translateY(20px)', 'y')).toBe(20); |
|||
}); |
|||
|
|||
test('setTranslate should update and append translate values', () => { |
|||
const command = new ComponentDrag({}); |
|||
|
|||
expect(command.setTranslate('translateX(10px)', 'x', '15px')).toContain('translateX(15px)'); |
|||
expect(command.setTranslate('translateX(10px)', 'y', '20px')).toContain('translateY(20px)'); |
|||
}); |
|||
|
|||
test('run should require target option', () => { |
|||
const command = new ComponentDrag({}); |
|||
|
|||
expect(() => command.run({} as any, null, {} as any)).toThrow('Target option is required'); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,38 @@ |
|||
import ComponentEnter from '../../../../src/commands/view/ComponentEnter'; |
|||
|
|||
describe('ComponentEnter command', () => { |
|||
test('should select the first selectable child', () => { |
|||
const command = new ComponentEnter({}); |
|||
const firstSelectable = { id: 'child-1' }; |
|||
const component = { |
|||
components: jest.fn(() => [ |
|||
{ get: jest.fn(() => false) }, |
|||
{ get: jest.fn(() => true), ...firstSelectable }, |
|||
{ get: jest.fn(() => true) }, |
|||
]), |
|||
}; |
|||
const editor = { |
|||
Canvas: { hasFocus: jest.fn(() => true) }, |
|||
getSelectedAll: jest.fn(() => [component]), |
|||
select: jest.fn(), |
|||
}; |
|||
|
|||
command.run(editor as any); |
|||
|
|||
expect(editor.select).toHaveBeenCalledWith([expect.objectContaining(firstSelectable)]); |
|||
}); |
|||
|
|||
test('should do nothing if the canvas has no focus', () => { |
|||
const command = new ComponentEnter({}); |
|||
const editor = { |
|||
Canvas: { hasFocus: jest.fn(() => false) }, |
|||
getSelectedAll: jest.fn(), |
|||
select: jest.fn(), |
|||
}; |
|||
|
|||
command.run(editor as any); |
|||
|
|||
expect(editor.getSelectedAll).not.toHaveBeenCalled(); |
|||
expect(editor.select).not.toHaveBeenCalled(); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,61 @@ |
|||
import ComponentExit from '../../../../src/commands/view/ComponentExit'; |
|||
|
|||
describe('ComponentExit command', () => { |
|||
test('should select the first selectable parent', () => { |
|||
const command = new ComponentExit({}); |
|||
const selectableParent = { |
|||
get: jest.fn(() => true), |
|||
parent: jest.fn(), |
|||
}; |
|||
const nonSelectableParent = { |
|||
get: jest.fn(() => false), |
|||
parent: jest.fn(() => selectableParent), |
|||
}; |
|||
const component = { |
|||
parent: jest.fn(() => nonSelectableParent), |
|||
}; |
|||
const editor = { |
|||
Canvas: { hasFocus: jest.fn(() => true) }, |
|||
getSelectedAll: jest.fn(() => [component]), |
|||
select: jest.fn(), |
|||
}; |
|||
|
|||
command.run(editor as any, null, {}); |
|||
|
|||
expect(editor.select).toHaveBeenCalledWith([selectableParent]); |
|||
}); |
|||
|
|||
test('should select parent when forced even without canvas focus', () => { |
|||
const command = new ComponentExit({}); |
|||
const parent = { |
|||
get: jest.fn(() => true), |
|||
parent: jest.fn(), |
|||
}; |
|||
const component = { |
|||
parent: jest.fn(() => parent), |
|||
}; |
|||
const editor = { |
|||
Canvas: { hasFocus: jest.fn(() => false) }, |
|||
getSelectedAll: jest.fn(() => [component]), |
|||
select: jest.fn(), |
|||
}; |
|||
|
|||
command.run(editor as any, null, { force: true }); |
|||
|
|||
expect(editor.select).toHaveBeenCalledWith([parent]); |
|||
}); |
|||
|
|||
test('should do nothing if the canvas has no focus and force is not set', () => { |
|||
const command = new ComponentExit({}); |
|||
const editor = { |
|||
Canvas: { hasFocus: jest.fn(() => false) }, |
|||
getSelectedAll: jest.fn(), |
|||
select: jest.fn(), |
|||
}; |
|||
|
|||
command.run(editor as any, null, {}); |
|||
|
|||
expect(editor.getSelectedAll).not.toHaveBeenCalled(); |
|||
expect(editor.select).not.toHaveBeenCalled(); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,44 @@ |
|||
import ComponentNext from '../../../../src/commands/view/ComponentNext'; |
|||
|
|||
describe('ComponentNext command', () => { |
|||
test('should select the next selectable sibling', () => { |
|||
const command = new ComponentNext({}); |
|||
const nextSelectable = { get: jest.fn(() => true) }; |
|||
const notSelectable = { get: jest.fn(() => false) }; |
|||
const parent = { |
|||
components: jest.fn(() => ({ length: 3 })), |
|||
getChildAt: jest.fn((index: number) => { |
|||
if (index === 1) return notSelectable; |
|||
if (index === 2) return nextSelectable; |
|||
return null; |
|||
}), |
|||
}; |
|||
const selected = { |
|||
parent: jest.fn(() => parent), |
|||
index: jest.fn(() => 0), |
|||
}; |
|||
const editor = { |
|||
Canvas: { hasFocus: jest.fn(() => true) }, |
|||
getSelectedAll: jest.fn(() => [selected]), |
|||
select: jest.fn(), |
|||
}; |
|||
|
|||
command.run(editor as any); |
|||
|
|||
expect(editor.select).toHaveBeenCalledWith([nextSelectable]); |
|||
}); |
|||
|
|||
test('should do nothing if the canvas has no focus', () => { |
|||
const command = new ComponentNext({}); |
|||
const editor = { |
|||
Canvas: { hasFocus: jest.fn(() => false) }, |
|||
getSelectedAll: jest.fn(), |
|||
select: jest.fn(), |
|||
}; |
|||
|
|||
command.run(editor as any); |
|||
|
|||
expect(editor.getSelectedAll).not.toHaveBeenCalled(); |
|||
expect(editor.select).not.toHaveBeenCalled(); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,43 @@ |
|||
import ComponentPrev from '../../../../src/commands/view/ComponentPrev'; |
|||
|
|||
describe('ComponentPrev command', () => { |
|||
test('should select the previous selectable sibling', () => { |
|||
const command = new ComponentPrev({}); |
|||
const prevSelectable = { get: jest.fn(() => true) }; |
|||
const notSelectable = { get: jest.fn(() => false) }; |
|||
const parent = { |
|||
getChildAt: jest.fn((index: number) => { |
|||
if (index === 1) return notSelectable; |
|||
if (index === 0) return prevSelectable; |
|||
return null; |
|||
}), |
|||
}; |
|||
const selected = { |
|||
parent: jest.fn(() => parent), |
|||
index: jest.fn(() => 2), |
|||
}; |
|||
const editor = { |
|||
Canvas: { hasFocus: jest.fn(() => true) }, |
|||
getSelectedAll: jest.fn(() => [selected]), |
|||
select: jest.fn(), |
|||
}; |
|||
|
|||
command.run(editor as any); |
|||
|
|||
expect(editor.select).toHaveBeenCalledWith([prevSelectable]); |
|||
}); |
|||
|
|||
test('should do nothing if the canvas has no focus', () => { |
|||
const command = new ComponentPrev({}); |
|||
const editor = { |
|||
Canvas: { hasFocus: jest.fn(() => false) }, |
|||
getSelectedAll: jest.fn(), |
|||
select: jest.fn(), |
|||
}; |
|||
|
|||
command.run(editor as any); |
|||
|
|||
expect(editor.getSelectedAll).not.toHaveBeenCalled(); |
|||
expect(editor.select).not.toHaveBeenCalled(); |
|||
}); |
|||
}); |
|||
@ -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([]); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,37 @@ |
|||
import CopyComponent from '../../../../src/commands/view/CopyComponent'; |
|||
|
|||
describe('CopyComponent command', () => { |
|||
test('should set the clipboard with selected components', () => { |
|||
const command = new CopyComponent({}); |
|||
const set = jest.fn(); |
|||
const selected = [{ id: 'cmp-1' }, { id: 'cmp-2' }]; |
|||
const editor = { |
|||
getModel: jest.fn(() => ({ set })), |
|||
getSelectedAll: jest.fn(() => selected), |
|||
}; |
|||
|
|||
command.run(editor as any); |
|||
|
|||
expect(set).toHaveBeenCalledWith('clipboard', selected); |
|||
}); |
|||
|
|||
test('should use delegated copy target when available', () => { |
|||
const command = new CopyComponent({}); |
|||
const set = jest.fn(); |
|||
const delegated = { id: 'delegated' }; |
|||
const component = { |
|||
delegate: { |
|||
copy: jest.fn(() => delegated), |
|||
}, |
|||
}; |
|||
const editor = { |
|||
getModel: jest.fn(() => ({ set })), |
|||
getSelectedAll: jest.fn(() => [component]), |
|||
}; |
|||
|
|||
command.run(editor as any); |
|||
|
|||
expect(component.delegate.copy).toHaveBeenCalledWith(component); |
|||
expect(set).toHaveBeenCalledWith('clipboard', [delegated]); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,59 @@ |
|||
import ExportTemplate from '../../../../src/commands/view/ExportTemplate'; |
|||
|
|||
describe('ExportTemplate command', () => { |
|||
test('should open modal and update editors content', () => { |
|||
const command = new ExportTemplate({}); |
|||
const once = jest.fn(); |
|||
const open = jest.fn(() => ({ |
|||
getModel: jest.fn(() => ({ |
|||
once, |
|||
})), |
|||
})); |
|||
const htmlSetContent = jest.fn(); |
|||
const cssSetContent = jest.fn(); |
|||
const createViewer = jest |
|||
.fn() |
|||
.mockImplementationOnce(() => ({ setContent: htmlSetContent })) |
|||
.mockImplementationOnce(() => ({ setContent: cssSetContent })); |
|||
const render = jest.fn(function (this: any) { |
|||
return { el: document.createElement('div') }; |
|||
}); |
|||
const EditorView = jest.fn(() => ({ render })); |
|||
|
|||
command.em = { |
|||
CodeManager: { |
|||
createViewer, |
|||
getConfig: jest.fn(() => ({})), |
|||
EditorView, |
|||
}, |
|||
} as any; |
|||
command.id = 'core:open-code'; |
|||
|
|||
const sender = { set: jest.fn() }; |
|||
const editor = { |
|||
getConfig: jest.fn(() => ({ stylePrefix: 'gjs-', textViewCode: 'Code' })), |
|||
Modal: { open }, |
|||
CodeManager: {}, |
|||
getHtml: jest.fn(() => '<div>HTML</div>'), |
|||
getCss: jest.fn(() => '.cls{}'), |
|||
stopCommand: jest.fn(), |
|||
} as any; |
|||
|
|||
command.run(editor, sender, {}); |
|||
|
|||
expect(sender.set).toHaveBeenCalledWith('active', 0); |
|||
expect(open).toHaveBeenCalled(); |
|||
expect(htmlSetContent).toHaveBeenCalledWith('<div>HTML</div>'); |
|||
expect(cssSetContent).toHaveBeenCalledWith('.cls{}'); |
|||
expect(once).toHaveBeenCalledWith('change:open', expect.any(Function)); |
|||
}); |
|||
|
|||
test('stop should close the modal', () => { |
|||
const command = new ExportTemplate({}); |
|||
const close = jest.fn(); |
|||
|
|||
command.stop({ Modal: { close } } as any); |
|||
|
|||
expect(close).toHaveBeenCalledTimes(1); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,63 @@ |
|||
import CommandFullscreen from '../../../../src/commands/view/Fullscreen'; |
|||
import Editor from '../../../../src/editor'; |
|||
|
|||
describe('Fullscreen command', () => { |
|||
let editor: Editor; |
|||
let container: HTMLElement; |
|||
let requestFullscreen: jest.Mock<Promise<void>, []>; |
|||
let isFullscreen: boolean; |
|||
let exitFullscreen: jest.Mock; |
|||
|
|||
beforeEach(() => { |
|||
isFullscreen = false; |
|||
requestFullscreen = jest.fn(async () => { |
|||
isFullscreen = true; |
|||
}); |
|||
container = document.createElement('div'); |
|||
Object.defineProperty(container, 'requestFullscreen', { |
|||
configurable: true, |
|||
value: requestFullscreen, |
|||
}); |
|||
exitFullscreen = jest.fn(() => { |
|||
isFullscreen = false; |
|||
}); |
|||
|
|||
Object.defineProperty(document, 'fullscreenElement', { |
|||
configurable: true, |
|||
get: () => (isFullscreen ? container : null), |
|||
}); |
|||
|
|||
Object.defineProperty(document, 'exitFullscreen', { |
|||
configurable: true, |
|||
value: exitFullscreen, |
|||
}); |
|||
|
|||
editor = new Editor({ el: container } as any); |
|||
}); |
|||
|
|||
afterEach(() => { |
|||
editor.destroy(); |
|||
}); |
|||
|
|||
test('runs from canonical and legacy ids', () => { |
|||
const addSpy = jest.spyOn(document, 'addEventListener'); |
|||
const removeSpy = jest.spyOn(document, 'removeEventListener'); |
|||
|
|||
expect(editor.Commands.get('core:fullscreen')).toBeInstanceOf(CommandFullscreen); |
|||
expect(editor.Commands.get('fullscreen')).toBeInstanceOf(CommandFullscreen); |
|||
|
|||
editor.runCommand('core:fullscreen'); |
|||
expect(requestFullscreen).toHaveBeenCalledTimes(1); |
|||
expect(addSpy).toHaveBeenCalledWith('fullscreenchange', expect.any(Function)); |
|||
|
|||
editor.stopCommand('core:fullscreen'); |
|||
expect(exitFullscreen).toHaveBeenCalledTimes(1); |
|||
expect(removeSpy).toHaveBeenCalledWith('fullscreenchange', expect.any(Function)); |
|||
|
|||
editor.runCommand('fullscreen', { target: container }); |
|||
expect(requestFullscreen).toHaveBeenCalledTimes(2); |
|||
|
|||
editor.stopCommand('fullscreen'); |
|||
expect(exitFullscreen).toHaveBeenCalledTimes(2); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,42 @@ |
|||
import MoveComponent from '../../../../src/commands/view/MoveComponent'; |
|||
|
|||
describe('MoveComponent command', () => { |
|||
test('rollback should cancel drag on escape', () => { |
|||
const command = new MoveComponent({}); |
|||
const cancelDrag = jest.fn(); |
|||
command.sorter = { cancelDrag }; |
|||
|
|||
command.rollback({ which: 27 }, false); |
|||
|
|||
expect(cancelDrag).toHaveBeenCalledTimes(1); |
|||
}); |
|||
|
|||
test('stop should reset wrapper and remove helper classes', () => { |
|||
const command = new MoveComponent({}); |
|||
const wrapper: any = {}; |
|||
wrapper.css = jest.fn(() => wrapper); |
|||
wrapper.unbind = jest.fn(() => wrapper); |
|||
wrapper.removeClass = jest.fn(() => wrapper); |
|||
const removeBadgeClass = jest.fn(); |
|||
const removeHighlighterClass = jest.fn(); |
|||
command.$wrapper = wrapper; |
|||
command.$badge = { removeClass: removeBadgeClass }; |
|||
command.$hl = { removeClass: removeHighlighterClass }; |
|||
command.badgeClass = 'badge-warning'; |
|||
command.hoverClass = 'highlighter-warning'; |
|||
command.noSelClass = 'no-select'; |
|||
command.onHovered = jest.fn(); |
|||
command.stopSelectComponent = jest.fn(); |
|||
command.em = { setSelected: jest.fn() } as any; |
|||
command.toggleToolsEl = jest.fn(); |
|||
command.editor = { stopCommand: jest.fn() }; |
|||
|
|||
command.stop(); |
|||
|
|||
expect(removeBadgeClass).toHaveBeenCalledWith('badge-warning'); |
|||
expect(removeHighlighterClass).toHaveBeenCalledWith('highlighter-warning'); |
|||
expect(wrapper.css).toHaveBeenCalledWith('cursor', ''); |
|||
expect(wrapper.unbind).toHaveBeenCalledTimes(1); |
|||
expect(wrapper.removeClass).toHaveBeenCalledWith('no-select'); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,39 @@ |
|||
import OpenAssets from '../../../../src/commands/view/OpenAssets'; |
|||
|
|||
describe('OpenAssets command', () => { |
|||
test('open should open the modal', () => { |
|||
const onceClose = jest.fn(); |
|||
const command = new OpenAssets({}); |
|||
const editor = { |
|||
Modal: { |
|||
open: jest.fn(() => ({ onceClose })), |
|||
}, |
|||
stopCommand: jest.fn(), |
|||
} as any; |
|||
command.editor = editor; |
|||
command.am = { __customData: jest.fn() }; |
|||
command.config = { custom: false }; |
|||
command.title = 'Assets'; |
|||
|
|||
command.open('content'); |
|||
|
|||
expect(editor.Modal.open).toHaveBeenCalledWith({ title: 'Assets', content: 'content' }); |
|||
expect(onceClose).toHaveBeenCalledTimes(1); |
|||
}); |
|||
|
|||
test('stop should close the modal', () => { |
|||
const command = new OpenAssets({}); |
|||
const editor = { |
|||
Modal: { |
|||
close: jest.fn(), |
|||
}, |
|||
} as any; |
|||
command.editor = editor; |
|||
command.am = { __customData: jest.fn() }; |
|||
command.config = { custom: false }; |
|||
|
|||
command.stop(editor); |
|||
|
|||
expect(editor.Modal.close).toHaveBeenCalledTimes(1); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,33 @@ |
|||
import OpenBlocks from '../../../../src/commands/view/OpenBlocks'; |
|||
|
|||
describe('OpenBlocks command', () => { |
|||
test('open should show the container', () => { |
|||
const command = new OpenBlocks({}); |
|||
const panels = { |
|||
getPanel: jest.fn(() => ({ set: jest.fn(() => ({ trigger: jest.fn() })) })), |
|||
addPanel: jest.fn(), |
|||
}; |
|||
|
|||
command.container = document.createElement('div'); |
|||
command.editor = { Panels: panels } as any; |
|||
command.bm = { render: jest.fn(() => document.createElement('div')) }; |
|||
command.config = { custom: false, appendTo: '' }; |
|||
command.firstRender = false; |
|||
|
|||
command.open(); |
|||
|
|||
expect(command.container.style.display).toBe('block'); |
|||
}); |
|||
|
|||
test('stop should hide the container', () => { |
|||
const command = new OpenBlocks({}); |
|||
command.container = document.createElement('div'); |
|||
command.container.style.display = 'block'; |
|||
command.bm = { __customData: jest.fn() }; |
|||
command.config = { custom: false }; |
|||
|
|||
command.stop(); |
|||
|
|||
expect(command.container.style.display).toBe('none'); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,35 @@ |
|||
import OpenLayers from '../../../../src/commands/view/OpenLayers'; |
|||
|
|||
describe('OpenLayers command', () => { |
|||
test('should show layers container when opened', () => { |
|||
const command = new OpenLayers({}); |
|||
const panelTrigger = jest.fn(); |
|||
const panel = { set: jest.fn(() => ({ trigger: panelTrigger })) }; |
|||
const render = jest.fn(() => document.createElement('div')); |
|||
const editor = { |
|||
LayerManager: { |
|||
getConfig: jest.fn(() => ({})), |
|||
render, |
|||
}, |
|||
Panels: { |
|||
getPanel: jest.fn(() => panel), |
|||
addPanel: jest.fn(), |
|||
}, |
|||
} as any; |
|||
|
|||
command.run(editor); |
|||
|
|||
expect(panel.set).toHaveBeenCalled(); |
|||
expect(command.layers?.style.display).toBe('block'); |
|||
}); |
|||
|
|||
test('stop should hide layers container', () => { |
|||
const command = new OpenLayers({}); |
|||
command.layers = document.createElement('div'); |
|||
command.layers.style.display = 'block'; |
|||
|
|||
command.stop(); |
|||
|
|||
expect(command.layers.style.display).toBe('none'); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,27 @@ |
|||
import OpenStyleManager from '../../../../src/commands/view/OpenStyleManager'; |
|||
|
|||
describe('OpenStyleManager command', () => { |
|||
test('toggleSm should show content when a target is selected', () => { |
|||
const command = new OpenStyleManager({}); |
|||
command.sender = { get: jest.fn(() => true) }; |
|||
command.sm = { getSelected: jest.fn(() => true) }; |
|||
command.$cntInner = { show: jest.fn(), hide: jest.fn() }; |
|||
command.$header = { show: jest.fn(), hide: jest.fn() }; |
|||
|
|||
command.toggleSm(); |
|||
|
|||
expect(command.$cntInner.show).toHaveBeenCalledTimes(1); |
|||
expect(command.$header.hide).toHaveBeenCalledTimes(1); |
|||
}); |
|||
|
|||
test('stop should hide content and header', () => { |
|||
const command = new OpenStyleManager({}); |
|||
command.$cntInner = { hide: jest.fn() }; |
|||
command.$header = { hide: jest.fn() }; |
|||
|
|||
command.stop(); |
|||
|
|||
expect(command.$cntInner.hide).toHaveBeenCalledTimes(1); |
|||
expect(command.$header.hide).toHaveBeenCalledTimes(1); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,27 @@ |
|||
import OpenTraitManager from '../../../../src/commands/view/OpenTraitManager'; |
|||
|
|||
describe('OpenTraitManager command', () => { |
|||
test('toggleTm should show content when a single target is selected', () => { |
|||
const command = new OpenTraitManager({}); |
|||
command.sender = { get: jest.fn(() => true) }; |
|||
command.target = { getSelectedAll: jest.fn(() => [{}]) }; |
|||
command.$cn2 = { show: jest.fn(), hide: jest.fn() }; |
|||
command.$header = { show: jest.fn(), hide: jest.fn() }; |
|||
|
|||
command.toggleTm(); |
|||
|
|||
expect(command.$cn2.show).toHaveBeenCalledTimes(1); |
|||
expect(command.$header.hide).toHaveBeenCalledTimes(1); |
|||
}); |
|||
|
|||
test('stop should hide content and header', () => { |
|||
const command = new OpenTraitManager({}); |
|||
command.$cn2 = { hide: jest.fn() }; |
|||
command.$header = { hide: jest.fn() }; |
|||
|
|||
command.stop(); |
|||
|
|||
expect(command.$cn2.hide).toHaveBeenCalledTimes(1); |
|||
expect(command.$header.hide).toHaveBeenCalledTimes(1); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,53 @@ |
|||
import { ComponentsEvents } from '../../../../src/dom_components/types'; |
|||
import PasteComponent from '../../../../src/commands/view/PasteComponent'; |
|||
|
|||
describe('PasteComponent command', () => { |
|||
test('should paste a clone into the selected collection and emit paste event', () => { |
|||
const command = new PasteComponent({}); |
|||
const added = { id: 'added' }; |
|||
const collection = { |
|||
add: jest.fn(() => added), |
|||
}; |
|||
const selected = { |
|||
collection, |
|||
index: jest.fn(() => 2), |
|||
get: jest.fn((key: string) => (key === 'copyable' ? true : undefined)), |
|||
clone: jest.fn(() => ({ id: 'clone' })), |
|||
parent: jest.fn(), |
|||
}; |
|||
const clipboard = [selected]; |
|||
const emitUpdate = jest.fn(); |
|||
const trigger = jest.fn(); |
|||
const editor = { |
|||
getModel: jest.fn(() => ({ |
|||
get: jest.fn((key: string) => (key === 'clipboard' ? clipboard : undefined)), |
|||
})), |
|||
getSelected: jest.fn(() => ({ emitUpdate })), |
|||
getSelectedAll: jest.fn(() => [selected]), |
|||
trigger, |
|||
} as any; |
|||
|
|||
command.run(editor, null, { action: 'clone-component' }); |
|||
|
|||
expect(collection.add).toHaveBeenCalled(); |
|||
expect(trigger).toHaveBeenCalledWith(ComponentsEvents.paste, added); |
|||
expect(emitUpdate).toHaveBeenCalledTimes(1); |
|||
}); |
|||
|
|||
test('should do nothing without clipboard content or selection', () => { |
|||
const command = new PasteComponent({}); |
|||
const trigger = jest.fn(); |
|||
const editor = { |
|||
getModel: jest.fn(() => ({ |
|||
get: jest.fn(() => null), |
|||
})), |
|||
getSelected: jest.fn(() => null), |
|||
getSelectedAll: jest.fn(() => []), |
|||
trigger, |
|||
} as any; |
|||
|
|||
command.run(editor, null); |
|||
|
|||
expect(trigger).not.toHaveBeenCalled(); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,30 @@ |
|||
import Resize, { ConvertUnitsToPx } from '../../../../src/commands/view/Resize'; |
|||
|
|||
describe('Resize command', () => { |
|||
test('stop should blur the canvas resizer', () => { |
|||
const command = new Resize({}); |
|||
const blur = jest.fn(); |
|||
command.canvasResizer = { blur } as any; |
|||
|
|||
command.stop(); |
|||
|
|||
expect(blur).toHaveBeenCalledTimes(1); |
|||
}); |
|||
|
|||
test('convertPxToUnit should keep method name and convert pixels to percentage', () => { |
|||
const command = new Resize({}); |
|||
const parent = document.createElement('div'); |
|||
Object.defineProperty(parent, 'offsetWidth', { configurable: true, value: 200 }); |
|||
const el = document.createElement('div'); |
|||
parent.appendChild(el); |
|||
|
|||
const result = command.convertPxToUnit({ |
|||
el, |
|||
valuePx: 50, |
|||
unit: ConvertUnitsToPx.perc, |
|||
elComputedStyle: window.getComputedStyle(el), |
|||
}); |
|||
|
|||
expect(result).toBe('25%'); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,30 @@ |
|||
import SelectComponent from '../../../../src/commands/view/SelectComponent'; |
|||
|
|||
describe('SelectComponent command', () => { |
|||
test('select should update editor selection and initialize resize', () => { |
|||
const command = new SelectComponent({}); |
|||
const selected = { id: 'cmp-selected' }; |
|||
const setSelected = jest.fn(); |
|||
const getSelected = jest.fn(() => selected); |
|||
command.em = { setSelected, getSelected } as any; |
|||
command.initResize = jest.fn(); |
|||
const model = { id: 'cmp-1' } as any; |
|||
const event = {} as MouseEvent; |
|||
|
|||
command.select(model, event); |
|||
|
|||
expect(setSelected).toHaveBeenCalledWith(model, { event, useValid: true }); |
|||
expect(command.initResize).toHaveBeenCalledWith(selected); |
|||
}); |
|||
|
|||
test('hideBadge should hide the badge element', () => { |
|||
const command = new SelectComponent({}); |
|||
const badge = document.createElement('div'); |
|||
badge.style.display = 'block'; |
|||
command.getBadge = jest.fn(() => badge); |
|||
|
|||
command.hideBadge(); |
|||
|
|||
expect(badge.style.display).toBe('none'); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,31 @@ |
|||
import SelectPosition from '../../../../src/commands/view/SelectPosition'; |
|||
|
|||
describe('SelectPosition command', () => { |
|||
test('nearFloat should detect floating neighbors', () => { |
|||
const command = new SelectPosition({}); |
|||
const dims = [ |
|||
[0, 0, 0, 0, true], |
|||
[0, 0, 0, 0, false], |
|||
]; |
|||
|
|||
expect(command.nearFloat(1, 'before', dims)).toBe(1); |
|||
}); |
|||
|
|||
test('stop should cancel drag and reset wrapper cursor', () => { |
|||
const command = new SelectPosition({}); |
|||
const wrapper: any = {}; |
|||
wrapper.css = jest.fn(() => wrapper); |
|||
wrapper.unbind = jest.fn(() => wrapper); |
|||
command.$wrapper = wrapper; |
|||
command.sorter = { cancelDrag: jest.fn() }; |
|||
command.cDim = []; |
|||
command.posMethod = 'before'; |
|||
command.posIndex = 0; |
|||
|
|||
command.stop(); |
|||
|
|||
expect(command.sorter.cancelDrag).toHaveBeenCalledTimes(1); |
|||
expect(wrapper.css).toHaveBeenCalledWith('cursor', ''); |
|||
expect(wrapper.unbind).toHaveBeenCalledTimes(1); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,34 @@ |
|||
import ShowOffset from '../../../../src/commands/view/ShowOffset'; |
|||
|
|||
describe('ShowOffset command', () => { |
|||
test('getOffsetMethod should build the method name from state', () => { |
|||
const command = new ShowOffset({}); |
|||
|
|||
expect(command.getOffsetMethod('Fixed')).toBe('getFixedOffsetViewerEl'); |
|||
}); |
|||
|
|||
test('run should stop the command when offsets are disabled', () => { |
|||
const command = new ShowOffset({ em: { getZoomDecimal: jest.fn(() => 1) } }); |
|||
command.id = 'core:component-offset'; |
|||
const editor = { |
|||
getConfig: jest.fn(() => ({ showOffsets: false, showOffsetsSelected: true })), |
|||
stopCommand: jest.fn(), |
|||
}; |
|||
|
|||
command.run(editor as any, null, { el: document.createElement('div') }); |
|||
|
|||
expect(editor.stopCommand).toHaveBeenCalledWith('core:component-offset', { el: expect.any(HTMLElement) }); |
|||
}); |
|||
|
|||
test('stop should hide the offset viewer', () => { |
|||
const command = new ShowOffset({}); |
|||
const viewer = document.createElement('div'); |
|||
command.canvas = { |
|||
getFixedOffsetViewerEl: jest.fn(() => viewer), |
|||
} as any; |
|||
|
|||
command.stop({} as any, null, { state: 'Fixed' }); |
|||
|
|||
expect(viewer.style.opacity).toBe('0'); |
|||
}); |
|||
}); |
|||
Loading…
Reference in new issue