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
19 changes: 12 additions & 7 deletions src/components/settings/SettingsMacrosTabExpert.vue
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ export default class SettingsMacrosTabExpert extends Mixins(BaseMixin, ThemeMixi

private boolFormEdit = false
private editGroupId: string | null = ''
private searchMacros: string = ''
private searchMacros: string | null = ''

get groupColors() {
return [
Expand Down Expand Up @@ -397,18 +397,23 @@ export default class SettingsMacrosTabExpert extends Mixins(BaseMixin, ThemeMixi
return colors
}

get allMacros() {
const macros = this.$store.getters['printer/getMacros'] ?? []
return macros.filter((macro: PrinterStateMacro) => {
get allMacros(): PrinterStateMacro[] {
return this.$store.getters['printer/getMacros'] ?? []
}

get filteredMacros() {
const search = (this.searchMacros ?? '').toLowerCase()

return this.allMacros.filter((macro: PrinterStateMacro) => {
return (
macro.name.toLowerCase().includes(this.searchMacros.toLowerCase()) ||
macro.description?.toLowerCase().includes(this.searchMacros.toLowerCase())
macro.name.toLowerCase().includes(search) ||
(macro.description?.toLowerCase().includes(search) ?? false)
)
})
}

get availableMacros() {
return this.allMacros.filter((m: GuiMacrosStateMacrogroupMacro) => !this.editGroupUsedMacros.includes(m.name))
return this.filteredMacros.filter((m: PrinterStateMacro) => !this.editGroupUsedMacros.includes(m.name))
}

get groups() {
Expand Down
8 changes: 5 additions & 3 deletions src/components/settings/SettingsMacrosTabSimple.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,16 @@ import { PrinterStateMacro } from '@/store/printer/types'
})
export default class SettingsMacrosTabSimple extends Mixins(BaseMixin) {
mdiMagnify = mdiMagnify
searchMacros: string = ''
searchMacros: string | null = ''

get macros() {
const search = (this.searchMacros ?? '').toLowerCase()
const macros = this.$store.getters['printer/getMacros'] ?? []

return macros.filter((macro: PrinterStateMacro) => {
return (
macro.name.toLowerCase().includes(this.searchMacros.toLowerCase()) ||
macro.description?.toLowerCase().includes(this.searchMacros.toLowerCase())
macro.name.toLowerCase().includes(search) ||
(macro.description?.toLowerCase().includes(search) ?? false)
)
})
}
Expand Down
2 changes: 1 addition & 1 deletion src/store/printer/getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export const getters: GetterTree<PrinterState, RootState> = {

getMacros: (state) => {
const array: PrinterStateMacro[] = []
const settings = state.configfile?.settings ?? null
const settings = state.configfile?.settings ?? {}
const printerGcodes = state.gcode?.commands ?? {}

const prefix = 'gcode_macro '
Expand Down
143 changes: 143 additions & 0 deletions tests/components/settings/settingsMacrosTabExpert.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { describe, expect, it } from 'vitest'
import SettingsMacrosTabExpert from '@/components/settings/SettingsMacrosTabExpert.vue'
import type { PrinterStateMacro } from '@/store/printer/types'
import type { GuiMacrosStateMacrogroup } from '@/store/gui/macros/types'

type ComponentOptions = {
macros?: Partial<PrinterStateMacro>[]
group?: Partial<GuiMacrosStateMacrogroup>
search?: string | null
}

interface MacrosTabExpert {
searchMacros: string | null
editGroupId: string | null
allMacros: PrinterStateMacro[]
filteredMacros: PrinterStateMacro[]
availableMacros: PrinterStateMacro[]
existsMacro(macroname: string): boolean
getMacroDescription(macroname: string): string | null
}

const MacrosTabExpertClass = SettingsMacrosTabExpert as unknown as new () => MacrosTabExpert

const createComponent = (options: ComponentOptions = {}) => {
const component = new MacrosTabExpertClass()

Object.defineProperty(component, '$store', {
value: {
getters: {
'printer/getMacros': options.macros ?? [],
'gui/macros/getMacrogroup': () => options.group,
},
},
})

Object.defineProperty(component, '$t', { value: (key: string) => key })

component.searchMacros = 'search' in options ? (options.search as string | null) : ''
component.editGroupId = 'group-1'

return component
}

const macroNames = (macros: PrinterStateMacro[]) => macros.map((macro) => macro.name)

describe('SettingsMacrosTabExpert', () => {
describe('the search field only filters the available macros', () => {
it('keeps a group macro recognized while the search hides it', () => {
const component = createComponent({
macros: [{ name: 'START_PRINT' }, { name: 'END_PRINT' }],
search: 'START',
})

expect(component.existsMacro('END_PRINT')).toBe(true)
})

it('keeps returning the real description of a macro hidden by the search', () => {
const component = createComponent({
macros: [
{ name: 'START_PRINT', description: 'Heats up and homes' },
{ name: 'END_PRINT', description: 'Parks the toolhead' },
],
search: 'START',
})

expect(component.getMacroDescription('END_PRINT')).toBe('Parks the toolhead')
})

it('still narrows the available macros list by name', () => {
const component = createComponent({
macros: [{ name: 'START_PRINT' }, { name: 'END_PRINT' }],
search: 'end',
})

expect(macroNames(component.availableMacros)).toStrictEqual(['END_PRINT'])
})

it('still narrows the available macros list by description', () => {
const component = createComponent({
macros: [
{ name: 'START_PRINT', description: 'Heats up and homes' },
{ name: 'END_PRINT', description: 'Parks the toolhead' },
],
search: 'parks',
})

expect(macroNames(component.availableMacros)).toStrictEqual(['END_PRINT'])
})

it('excludes macros already used in the edited group', () => {
const component = createComponent({
macros: [{ name: 'START_PRINT' }, { name: 'END_PRINT' }],
group: { macros: [{ name: 'START_PRINT', pos: 1 }] as GuiMacrosStateMacrogroup['macros'] },
})

expect(macroNames(component.availableMacros)).toStrictEqual(['END_PRINT'])
})
})

describe('a cleared search field', () => {
it('does not throw when the search is null', () => {
const component = createComponent({
macros: [{ name: 'START_PRINT' }],
search: null,
})

expect(() => component.availableMacros).not.toThrow()
expect(macroNames(component.availableMacros)).toStrictEqual(['START_PRINT'])
})

it('does not throw when a macro has no description', () => {
const component = createComponent({
macros: [{ name: 'START_PRINT', description: null }],
search: 'nomatch',
})

expect(() => component.availableMacros).not.toThrow()
expect(component.availableMacros).toStrictEqual([])
})
})

describe('deleted macro detection', () => {
it('reports a macro that is no longer in the config as deleted', () => {
const component = createComponent({ macros: [{ name: 'START_PRINT' }] })

expect(component.existsMacro('REMOVED_MACRO')).toBe(false)
expect(component.getMacroDescription('REMOVED_MACRO')).toBe('Settings.MacrosTab.DeletedMacro')
})

it('matches macro names case-insensitively', () => {
const component = createComponent({ macros: [{ name: 'Start_Print' }] })

expect(component.existsMacro('START_PRINT')).toBe(true)
expect(component.existsMacro('start_print')).toBe(true)
})

it('returns null instead of a description when the macro has no help text', () => {
const component = createComponent({ macros: [{ name: 'START_PRINT' }] })

expect(component.getMacroDescription('START_PRINT')).toBeNull()
})
})
})
118 changes: 118 additions & 0 deletions tests/components/settings/settingsMacrosTabSimple.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { describe, expect, it, vi } from 'vitest'
import SettingsMacrosTabSimple from '@/components/settings/SettingsMacrosTabSimple.vue'
import type { PrinterStateMacro } from '@/store/printer/types'

type ComponentOptions = {
macros?: Partial<PrinterStateMacro>[]
hiddenMacros?: string[]
search?: string | null
}

interface MacrosTabSimple {
searchMacros: string | null
macros: PrinterStateMacro[]
hiddenMacros: string[]
getMacroStatus(name: string): boolean
changeMacroStatus(name: string): void
}

const MacrosTabSimpleClass = SettingsMacrosTabSimple as unknown as new () => MacrosTabSimple

const createComponent = (options: ComponentOptions = {}) => {
const dispatch = vi.fn()
const component = new MacrosTabSimpleClass()

Object.defineProperty(component, '$store', {
value: {
state: {
gui: { macros: { hiddenMacros: options.hiddenMacros ?? [] } },
},
getters: {
'printer/getMacros': options.macros ?? [],
},
dispatch,
},
})

component.searchMacros = 'search' in options ? (options.search as string | null) : ''

return { component, dispatch }
}

const macroNames = (macros: PrinterStateMacro[]) => macros.map((macro) => macro.name)

describe('SettingsMacrosTabSimple', () => {
describe('search', () => {
it('filters by macro name', () => {
const { component } = createComponent({
macros: [{ name: 'START_PRINT' }, { name: 'END_PRINT' }],
search: 'end',
})

expect(macroNames(component.macros)).toStrictEqual(['END_PRINT'])
})

it('filters by macro description', () => {
const { component } = createComponent({
macros: [
{ name: 'START_PRINT', description: 'Heats up and homes' },
{ name: 'END_PRINT', description: 'Parks the toolhead' },
],
search: 'parks',
})

expect(macroNames(component.macros)).toStrictEqual(['END_PRINT'])
})

it('does not throw and lists every macro when the search is null', () => {
const { component } = createComponent({
macros: [{ name: 'START_PRINT' }, { name: 'END_PRINT' }],
search: null,
})

expect(() => component.macros).not.toThrow()
expect(macroNames(component.macros)).toStrictEqual(['START_PRINT', 'END_PRINT'])
})

it('does not throw when a macro has no description', () => {
const { component } = createComponent({
macros: [{ name: 'START_PRINT', description: null }],
search: 'nomatch',
})

expect(() => component.macros).not.toThrow()
expect(component.macros).toStrictEqual([])
})
})

describe('hiding macros', () => {
it('reports a macro as enabled when it is not hidden', () => {
const { component } = createComponent({ hiddenMacros: ['END_PRINT'] })

expect(component.getMacroStatus('START_PRINT')).toBe(true)
expect(component.getMacroStatus('END_PRINT')).toBe(false)
})

it('hides a visible macro', () => {
const { component, dispatch } = createComponent({ hiddenMacros: [] })

component.changeMacroStatus('Start_Print')

expect(dispatch).toHaveBeenCalledWith('gui/macros/saveSetting', {
name: 'hiddenMacros',
value: ['START_PRINT'],
})
})

it('unhides an already hidden macro', () => {
const { component, dispatch } = createComponent({ hiddenMacros: ['START_PRINT', 'END_PRINT'] })

component.changeMacroStatus('START_PRINT')

expect(dispatch).toHaveBeenCalledWith('gui/macros/saveSetting', {
name: 'hiddenMacros',
value: ['END_PRINT'],
})
})
})
})
Loading
Loading