Name the toner on the forecast, keep the number on the hover
The forecast identified a cartridge by its part number alone. 'W2020X' only tells you which toner it is once you already know, and both places that name a supply - the Order now list and the Part column - showed nothing else, so the report read as a list of codes. Both now show marketingname, which is what the box says: '414X Black'. The part number moves to the title attribute, one hover away, because it is still the thing an order is placed against. A supply with no name on file falls back to the number rather than showing an empty cell; marketingname is nullable. 'Copy list' is deliberately untouched. It writes the part number verbatim, which is what purchasing pastes into an order, and a spec pins that the name does NOT reach that text. firstPart() now returns the whole part row rather than its number, since the cell needs both halves. It has one caller. Dropped the .partnumber style with its .unmapped variant: nothing renders that class any more, and the variant was already dead - the unmapped block lists no part number by definition. Verified by mounting the component against a fixture. The dev box has no Zabbix, so the page itself has no rows to look at there.
This commit is contained in:
110
plugins/printers/frontend/views/tonerForecastNames.spec.js
Normal file
110
plugins/printers/frontend/views/tonerForecastNames.spec.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { mount, flushPromises } from '@vue/test-utils'
|
||||
|
||||
// The forecast identifies a cartridge by its NAME - '414X Black' - and keeps the
|
||||
// part number on the hover. A part number alone ('W2020X') only identifies the
|
||||
// toner to someone who already knows it, and the two places that name a supply
|
||||
// (the order list and the Part column) had shown nothing else.
|
||||
//
|
||||
// The number has to survive somewhere: it is what an order is placed against.
|
||||
// It stays in the title attribute and, verbatim and unlabelled, in the text that
|
||||
// 'Copy list' writes to the clipboard. These specs pin both halves.
|
||||
//
|
||||
// marketingname is nullable, so every display falls back to the number.
|
||||
|
||||
vi.mock('@/api', () => ({
|
||||
printersApi: { supplyForecast: vi.fn() },
|
||||
}))
|
||||
vi.mock('@/utils/apiError', () => ({ apiError: (e) => String(e) }))
|
||||
|
||||
import { printersApi } from '@/api'
|
||||
import TonerForecast from './TonerForecast.vue'
|
||||
|
||||
function payload({ marketingname = '414X Black', partnumber = 'W2020X' } = {}) {
|
||||
const part = { partnumber, marketingname, capacitytier: 'high', pageyield: 7500 }
|
||||
return {
|
||||
data: {
|
||||
data: {
|
||||
available: true,
|
||||
days: 90,
|
||||
horizondays: 14,
|
||||
summary: { bands: { empty: 1 }, replacements: 3, toorder: 1 },
|
||||
cartridges: [{
|
||||
printerid: 1, printername: 'GageLab-Xerox-C415', assetnumber: 'P-1',
|
||||
ipaddress: '10.49.215.12', name: 'Black toner', color: 'black',
|
||||
currentlevel: 4, band: 'empty', daysleft: 0, burnrateperday: 1.8,
|
||||
rateunstable: false, replacements: 3, basisdays: 12,
|
||||
partnumbers: [part],
|
||||
}],
|
||||
unestimated: [],
|
||||
orderlist: [{
|
||||
partnumber, marketingname, color: 'black', supplytype: 'toner',
|
||||
model: 'Versalink C415', alternates: [], quantity: 2,
|
||||
printers: [{ printerid: 1, printername: 'GageLab-Xerox-C415', daysleft: 0 }],
|
||||
}],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function render(overrides) {
|
||||
printersApi.supplyForecast.mockResolvedValue(payload(overrides))
|
||||
const wrapper = mount(TonerForecast, {
|
||||
global: { stubs: { 'router-link': { template: '<a><slot /></a>' } } },
|
||||
})
|
||||
await flushPromises()
|
||||
return wrapper
|
||||
}
|
||||
|
||||
describe('the supply is named, not numbered', () => {
|
||||
it('shows the name in the order list', async () => {
|
||||
const wrapper = await render()
|
||||
const name = wrapper.find('.order-list .supplyname')
|
||||
expect(name.text()).toBe('414X Black')
|
||||
})
|
||||
|
||||
it('shows the name in the Part column', async () => {
|
||||
const wrapper = await render()
|
||||
const cells = wrapper.findAll('tbody .supplyname')
|
||||
expect(cells.length).toBeGreaterThan(0)
|
||||
expect(cells[0].text()).toBe('414X Black')
|
||||
})
|
||||
|
||||
it('keeps the part number on the hover, both places', async () => {
|
||||
const wrapper = await render()
|
||||
for (const el of wrapper.findAll('.supplyname')) {
|
||||
expect(el.attributes('title')).toBe('W2020X')
|
||||
}
|
||||
})
|
||||
|
||||
it('never renders the part number as the visible label', async () => {
|
||||
const wrapper = await render()
|
||||
for (const el of wrapper.findAll('.supplyname')) {
|
||||
expect(el.text()).not.toBe('W2020X')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('a supply with no name on file', () => {
|
||||
it('falls back to the part number rather than showing nothing', async () => {
|
||||
const wrapper = await render({ marketingname: null })
|
||||
for (const el of wrapper.findAll('.supplyname')) {
|
||||
expect(el.text()).toBe('W2020X')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('what purchasing receives is unchanged', () => {
|
||||
it('copies the part number, not the name', async () => {
|
||||
const written = []
|
||||
Object.assign(navigator, {
|
||||
clipboard: { writeText: (text) => { written.push(text); return Promise.resolve() } },
|
||||
})
|
||||
const wrapper = await render()
|
||||
await wrapper.find('.order-head button').trigger('click')
|
||||
await flushPromises()
|
||||
expect(written).toHaveLength(1)
|
||||
expect(written[0]).toContain('W2020X')
|
||||
expect(written[0]).not.toContain('414X Black')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user