Skip to content
Open
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
52 changes: 49 additions & 3 deletions src/__tests__/components/IconRendererModule.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,10 @@ describe('IconRendererModule — canRender', () => {
})

describe('IconRendererModule — drawShape', () => {
it('delegates to the bpmn renderer handler for the matching base type with renderIcon=false', () => {
it('delegates to the bpmn renderer handler for activities without forcing renderIcon=false', () => {
// For activities we want the default task-type marker (user figure /
// gears / ...) to stay visible alongside the template icon, so we
// must NOT suppress it via renderIcon=false.
const { instance, handlers } = makeRenderer({
template: { icon: { contents: ICON_DATA_URI } },
})
Expand All @@ -174,9 +177,49 @@ describe('IconRendererModule — drawShape', () => {
const [gfxArg, elArg, attrsArg] = handlers['bpmn:Task'].mock.calls[0]
expect(gfxArg).toBe(parentGfx)
expect(elArg).toBe(el)
expect(attrsArg).toEqual({ foo: 'bar' })
})

it('passes renderIcon=false for events so the template icon replaces the default marker', () => {
const { instance, handlers } = makeRenderer({
template: { icon: { contents: ICON_DATA_URI } },
})
const el = makeElement({
type: 'bpmn:StartEvent',
instanceOfTypes: ['bpmn:StartEvent', 'bpmn:Event'],
width: 36,
height: 36,
})
const parentGfx = document.createElementNS('http://www.w3.org/2000/svg', 'g')

instance.drawShape(parentGfx, el, { foo: 'bar' })

const [, , attrsArg] = handlers['bpmn:StartEvent'].mock.calls[0]
expect(attrsArg).toEqual({ foo: 'bar', renderIcon: false })
})

it('prefers a handler keyed by element.type over the base-type fallback', () => {
// Without this the .find() over base types returns 'bpmn:Task' first
// for any task subtype, and handlers['bpmn:Task'] draws a plain task
// with no user figure / gears / etc.
const { instance, bpmnRenderer, handlers } = makeRenderer({
template: { icon: { contents: ICON_DATA_URI } },
})
bpmnRenderer.handlers['bpmn:UserTask'] = vi.fn(
(parentGfx) => ({ tag: 'userTask-gfx', parentGfx })
)
const el = makeElement({
type: 'bpmn:UserTask',
instanceOfTypes: ['bpmn:UserTask', 'bpmn:Task', 'bpmn:Activity'],
})
const parentGfx = document.createElementNS('http://www.w3.org/2000/svg', 'g')

instance.drawShape(parentGfx, el)

expect(bpmnRenderer.handlers['bpmn:UserTask']).toHaveBeenCalledTimes(1)
expect(handlers['bpmn:Task']).not.toHaveBeenCalled()
})

it('returns the gfx produced by the delegated handler', () => {
const { instance, handlers } = makeRenderer({
template: { icon: { contents: ICON_DATA_URI } },
Expand Down Expand Up @@ -213,7 +256,9 @@ describe('IconRendererModule — drawShape', () => {
expect(img.getAttribute('height')).toBe('18')
})

it('positions the icon at fixed top-left padding for Activities', () => {
it('positions the icon in the top-right corner for Activities', () => {
// The top-left is reserved for the default BPMN task-type marker, so
// the template icon goes in the opposite corner.
const { instance } = makeRenderer({
template: { icon: { contents: ICON_DATA_URI } },
})
Expand All @@ -228,7 +273,8 @@ describe('IconRendererModule — drawShape', () => {
instance.drawShape(parentGfx, el)

const img = parentGfx.querySelector('image')
expect(img.getAttribute('x')).toBe('5')
// width(100) - size(18) - padding(5) = 77
expect(img.getAttribute('x')).toBe('77')
expect(img.getAttribute('y')).toBe('5')
})

Expand Down
20 changes: 16 additions & 4 deletions src/components/modeler/element-templates/IconRendererModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,25 @@ ElementTemplateIconRenderer.prototype.drawShape = function(parentGfx, element, a
'bpmn:SubProcess'
].find(t => is(element, t))

const renderer = this._bpmnRenderer.handlers[baseType]
const gfx = renderer(parentGfx, element, { ...attrs, renderIcon: false })
// Prefer the specific handler (e.g. 'bpmn:UserTask') so the task-type
// marker — the user figure on a user task, gears on a service task — is
// drawn. Falling back to the base-type handler (e.g. 'bpmn:Task') would
// draw a plain task with no marker. Keep the baseType fallback for custom
// subclasses that don't register their own handler.
const handlers = this._bpmnRenderer.handlers
const renderer = handlers[element.type] || handlers[baseType]
const isActivity = is(element, 'bpmn:Activity')
// For activities, keep the default task-type marker (user figure, gears, ...)
// visible and paint the template icon in the opposite (top-right) corner.
// For events there isn't room for both, so we still replace the default
// marker with the template icon in the center.
const rendererAttrs = isActivity ? attrs : { ...attrs, renderIcon: false }
const gfx = renderer(parentGfx, element, rendererAttrs)

const icon = this._getIcon(element)
const size = 18
const padding = is(element, 'bpmn:Activity')
? { x: 5, y: 5 }
const padding = isActivity
? { x: element.width - size - 5, y: 5 }
: { x: (element.width - size) / 2, y: (element.height - size) / 2 }

const img = svgCreate('image')
Expand Down