Skip to content

Commit 019f14d

Browse files
authored
Merge pull request #1218
* refactor(36682): add a custom MSW intercept logic for Pulse * refactor(36682): refactor the MSW mocks for activation * refactor(36682): add intercept for pulse status * refactor(36682): add intercept for pulse assets * refactor(36682): add intercept for asset mappers * refactor(36682): support clean/preloaded states * refactor(36682): update onboarding PO * refactor(36682): update shell PO * refactor(36682): update Assets List PO * tests(36682): add basic E2E tests for Pulse * tests(36682): add mocks * refactor(36682): add custom label with add-on * refactor(36682): refactor the "more info" widget on labels * refactor(36682): refactor the "more info" widget on labels * test(36682): fix mocks * test(36682): add tests * test(36682): add intercepts for PUT assets/asset mappers * test(36682): add PO for asset modal * test(36682): fix bug in getter * test(36682): add E2E tests * fix(36682): linting * test(36682): fix tests * test(36682): fix tests * fix(36963): add PO for the mapper wizard * fix(36963): update PO * fix(36963): update intercepts * test(36963): add tests * test(36682): a bit of cleaning * chore(36682): CI trigger * chore(36682): a bit of cleaning * chore(36682): a bit of cleaning
1 parent e96f5bb commit 019f14d

24 files changed

+644
-105
lines changed

.github/workflows/trigger.md

Whitespace-only changes.

hivemq-edge-frontend/cypress/e2e/pulse/activation.spec.cy.ts

Lines changed: 6 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,19 @@
11
import { MOCK_JWT } from '@/__test-utils__/mocks.ts'
2-
import type { ProtocolAdapter } from '@/api/__generated__'
3-
import { MOCK_CAPABILITY_PERSISTENCE, MOCK_CAPABILITY_PULSE_ASSETS } from '@/api/hooks/useFrontendServices/__handlers__'
4-
import { drop, factory, primaryKey } from '@mswjs/data'
2+
import { drop } from '@mswjs/data'
53

6-
import { cy_interceptCoreE2E } from 'cypress/utils/intercept.utils.ts'
74
import { loginPage, homePage, pulseActivationPanel } from 'cypress/pages'
8-
import { ONBOARDING } from '../../utils/constants.utils.ts'
5+
import { cy_interceptCoreE2E } from 'cypress/utils/intercept.utils.ts'
6+
import { cy_interceptPulseWithMockDB, getPulseFactory } from 'cypress/utils/intercept-pulse.utils.ts'
7+
import { ONBOARDING } from 'cypress/utils/constants.utils.ts'
98

109
describe('Pulse Agent Activation', () => {
11-
const mswDB = factory({
12-
capabilities: {
13-
id: primaryKey(String),
14-
json: String,
15-
},
16-
})
10+
const mswDB = getPulseFactory()
1711

1812
beforeEach(() => {
1913
drop(mswDB)
2014

2115
cy_interceptCoreE2E()
22-
23-
// Load the mock capabilities into the mock databases
24-
mswDB.capabilities.create({
25-
id: 'capabilities',
26-
json: JSON.stringify({
27-
items: [MOCK_CAPABILITY_PERSISTENCE],
28-
}),
29-
})
30-
31-
cy.intercept<ProtocolAdapter>('GET', '/api/v1/frontend/capabilities', (req) => {
32-
const data = mswDB.capabilities.findFirst({
33-
where: {
34-
id: {
35-
equals: 'capabilities',
36-
},
37-
},
38-
})
39-
req.reply(200, JSON.parse(data.json))
40-
}).as('getCapabilities')
41-
42-
cy.intercept<ProtocolAdapter>('POST', '/api/v1/management/pulse/activation-token', (req) => {
43-
mswDB.capabilities.update({
44-
where: {
45-
id: {
46-
equals: 'capabilities',
47-
},
48-
},
49-
50-
data: {
51-
json: JSON.stringify({
52-
items: [MOCK_CAPABILITY_PERSISTENCE, MOCK_CAPABILITY_PULSE_ASSETS],
53-
}),
54-
},
55-
})
56-
req.reply(200)
57-
})
16+
cy_interceptPulseWithMockDB(mswDB)
5817

5918
// There seems to be a bug in the CI without something in the path
6019
loginPage.visit('/app/workspace')
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { formatTopicString } from '@/components/MQTT/topic-utils.ts'
2+
import { drop } from '@mswjs/data'
3+
4+
import { loginPage, homePage, assetsPage, workspacePage, rjsf } from 'cypress/pages'
5+
import { cy_interceptCoreE2E } from 'cypress/utils/intercept.utils.ts'
6+
import {
7+
cy_interceptPulseWithMockDB,
8+
getPulseFactory,
9+
MOCK_MAIN_ASSET_MAPPER_ID,
10+
} from 'cypress/utils/intercept-pulse.utils.ts'
11+
import { assetMappingWizard } from 'cypress/pages/Pulse/AssetMappingWizardForm.ts'
12+
import { ONBOARDING } from 'cypress/utils/constants.utils.ts'
13+
import { assetMapperForm } from '../../pages/Workspace/AssetMapperFormPage.ts'
14+
15+
describe('Pulse Assets', () => {
16+
const mswDB = getPulseFactory()
17+
18+
beforeEach(() => {
19+
drop(mswDB)
20+
21+
cy_interceptCoreE2E()
22+
cy_interceptPulseWithMockDB(mswDB, true, true)
23+
24+
loginPage.visit('/app/workspace')
25+
loginPage.loginButton.click()
26+
homePage.navLink.click()
27+
})
28+
29+
describe('Assets Management', () => {
30+
it('should render assets', () => {
31+
homePage.taskSectionTitle(ONBOARDING.TASK_PULSE, 0).should('contain.text', 'Pulse is currently active.')
32+
33+
homePage.pulseOnboarding.title.should('contain.text', 'Stay up-to-date with your asset mappings')
34+
35+
const expectedTodoSummary = [2, 0, 0]
36+
homePage.pulseOnboarding.todos.should('have.length', expectedTodoSummary.length)
37+
homePage.pulseOnboarding.todosSummary.each(($element, idx) => {
38+
cy.wrap($element).should('contain.text', expectedTodoSummary[idx])
39+
})
40+
homePage.pulseOnboarding.todos.eq(0).find('a').click()
41+
42+
assetsPage.location.should('equal', '/app/pulse-assets')
43+
assetsPage.table.rows.should('have.length', 2)
44+
45+
assetsPage.search.input.type('1234')
46+
assetsPage.search.clear.click()
47+
48+
assetsPage.search.filter(0).should('not.exist')
49+
assetsPage.search.filter('topic').type('topic{enter}')
50+
assetsPage.search.clearFilter('status').click()
51+
assetsPage.search.clearAll.click()
52+
53+
assetsPage.table.actions(0).should('have.attr', 'aria-haspopup', 'menu')
54+
assetsPage.table.action(0, 'map').click()
55+
})
56+
it.skip('should relate assets to asset mappers', () => {})
57+
})
58+
59+
describe('Asset Mapping', () => {
60+
it('should create a new asset mapper', () => {
61+
homePage.taskSectionTitle(ONBOARDING.TASK_PULSE, 0).should('contain.text', 'Pulse is currently active.')
62+
homePage.pulseOnboarding.todos.eq(0).find('a').click()
63+
assetsPage.location.should('equal', '/app/pulse-assets')
64+
assetsPage.table.action(0, 'map').click()
65+
66+
assetMappingWizard.form.should('be.visible').and('have.css', 'opacity', '1')
67+
assetMappingWizard.selectMapper.root.should('be.visible')
68+
assetMappingWizard.selectSources.root.should('not.exist')
69+
70+
assetMappingWizard.selectMapper.label.should('have.text', 'Asset mapper')
71+
assetMappingWizard.selectMapper.moreInfo.should('have.attr', 'aria-label', 'More information')
72+
assetMappingWizard.selectMapper.select.should('be.visible')
73+
assetMappingWizard.selectMapper.value.should('not.exist')
74+
assetMappingWizard.selectMapper.placeholder.should('have.text', 'Type or select ...')
75+
assetMappingWizard.selectMapper.helperText.should('have.text', 'The asset mapper to use for the new mapping')
76+
77+
assetMappingWizard.selectMapper.select.type('Non-existing mapper{enter}')
78+
assetMappingWizard.selectMapper.helperText.should(
79+
'have.text',
80+
'A new asset mapper will be created in the Workspace, with a predefined mapping for this asset'
81+
)
82+
83+
assetMappingWizard.selectSources.root.should('be.visible')
84+
assetMappingWizard.selectSources.label.should('have.text', 'Data Sources')
85+
assetMappingWizard.selectSources.moreInfo.should('have.attr', 'aria-label', 'More information')
86+
assetMappingWizard.selectSources.select.should('be.visible')
87+
assetMappingWizard.selectSources.values.should('have.length', 2)
88+
assetMappingWizard.selectSources.helperText.should(
89+
'have.text',
90+
'The data sources this new mapper will be initially connected to'
91+
)
92+
assetMappingWizard.selectSources.select.type('my-adapter{enter}')
93+
assetMappingWizard.submit.click()
94+
assetsPage.toast.error.should('be.visible')
95+
})
96+
97+
it('should add an asset to an existing mapper', () => {
98+
assetsPage.navLink.click()
99+
assetsPage.location.should('equal', '/app/pulse-assets')
100+
101+
// create the first default mapper
102+
assetsPage.table.action(0, 'map').click()
103+
assetMappingWizard.selectMapper.select.type('Non-existing mapper{enter}')
104+
assetMappingWizard.selectSources.select.type('my-adapter{enter}')
105+
assetMappingWizard.submit.click()
106+
107+
assetsPage.toast.success.should('be.visible')
108+
assetsPage.toast.close()
109+
110+
// TODO Check that the mapper has been created in the workspace
111+
workspacePage.location.should('equal', '/app/workspace')
112+
workspacePage.canvas.should('be.visible')
113+
workspacePage.toolbox.fit.click()
114+
115+
workspacePage
116+
.combinerNodeContent(MOCK_MAIN_ASSET_MAPPER_ID)
117+
.title.should('have.text', 'Non-existing mapper (new)')
118+
workspacePage
119+
.combinerNodeContent(MOCK_MAIN_ASSET_MAPPER_ID)
120+
.topic.should('have.text', formatTopicString('test/topic'))
121+
workspacePage.combinerNode(MOCK_MAIN_ASSET_MAPPER_ID).click()
122+
workspacePage.combinerNode(MOCK_MAIN_ASSET_MAPPER_ID).dblclick()
123+
assetMapperForm.formTab(2).click()
124+
125+
// delete the mapping, to allow editing of the name
126+
assetMapperForm.assetMappings.action(0, 'delete').click()
127+
assetMapperForm.field('mappings').table.noDataMessage.should('have.text', 'No data received yet.')
128+
129+
assetMapperForm.formTab(0).click()
130+
assetMapperForm.field('name').input.clear().type('my mapper')
131+
assetMapperForm.submit.click()
132+
133+
workspacePage.combinerNodeContent(MOCK_MAIN_ASSET_MAPPER_ID).title.should('have.text', 'my mapper')
134+
workspacePage.combinerNodeContent(MOCK_MAIN_ASSET_MAPPER_ID).topic.should('not.exist')
135+
})
136+
137+
it.skip('should remove an existing mapping', () => {})
138+
it.skip('should change an existing mapping', () => {})
139+
})
140+
})

hivemq-edge-frontend/cypress/pages/Home/HomePage.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ShellPage } from '../ShellPage.ts'
2-
import { EDGE_MENU_LINKS } from 'cypress/utils/constants.utils.ts'
2+
import { EDGE_MENU_LINKS, ONBOARDING } from 'cypress/utils/constants.utils.ts'
33

44
export class HomePage extends ShellPage {
55
get navLink() {
@@ -25,6 +25,24 @@ export class HomePage extends ShellPage {
2525
taskSection(task: number, section: number) {
2626
return cy.get('main aside').eq(task).find('section').eq(section)
2727
}
28+
29+
taskSectionTitle(task: number, section: number) {
30+
return cy.get('main aside').eq(task).find('section').eq(section).find('p').eq(0)
31+
}
32+
33+
pulseOnboarding = {
34+
get title() {
35+
return homePage.taskSection(ONBOARDING.TASK_PULSE, 2).find('p').first()
36+
},
37+
38+
get todos() {
39+
return homePage.taskSection(ONBOARDING.TASK_PULSE, 2).find('ul li')
40+
},
41+
42+
get todosSummary() {
43+
return homePage.taskSection(ONBOARDING.TASK_PULSE, 2).find('ul li span')
44+
},
45+
}
2846
}
2947

3048
export const homePage = new HomePage()

hivemq-edge-frontend/cypress/pages/Pulse/ActivationFormPage.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { RJSFomField } from '../RJSF/RJSFomField.ts'
1+
import { RJSFormField } from '../RJSF/RJSFormField.ts'
22

3-
export class ActivationFormPage extends RJSFomField {
3+
export class ActivationFormPage extends RJSFormField {
44
get trigger() {
55
return cy.getByTestId('pulse-activation-trigger')
66
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
export class AssetMappingWizardForm {
2+
get form() {
3+
return cy.get('section[role="dialog"]#chakra-modal-wizard-mapper')
4+
}
5+
6+
header = {
7+
get title() {
8+
return cy.get('section[role="dialog"]#chakra-modal-wizard-mapper').find('header')
9+
},
10+
}
11+
12+
get submit() {
13+
return cy.get('section[role="dialog"]#chakra-modal-wizard-mapper').find('footer button')
14+
}
15+
16+
get selectMapper() {
17+
return {
18+
get root() {
19+
return cy.getByTestId('wizard-mapper-selector-container')
20+
},
21+
22+
get label() {
23+
return cy.getByTestId('wizard-mapper-selector-container').find('label[for="asset-mapper"]')
24+
},
25+
26+
get moreInfo() {
27+
return cy.getByTestId('wizard-mapper-selector-container').find('[data-testid="more-info-trigger"]')
28+
},
29+
30+
get select() {
31+
return cy.getByTestId('wizard-mapper-selector-container').find('#wizard-mapper-selector')
32+
},
33+
34+
get value() {
35+
return cy.getByTestId('wizard-mapper-selector-container').find('#react-select-mapper-value')
36+
}, //react-select-mapper-placeholder
37+
38+
get placeholder() {
39+
return cy.getByTestId('wizard-mapper-selector-container').find('#react-select-mapper-placeholder')
40+
},
41+
42+
get helperText() {
43+
return cy
44+
.getByTestId('wizard-mapper-selector-container')
45+
.find('[data-testid="wizard-mapper-selector-instruction"]')
46+
},
47+
}
48+
}
49+
50+
get selectSources() {
51+
return {
52+
get root() {
53+
return cy.getByTestId('wizard-mapper-entities-container')
54+
},
55+
56+
get label() {
57+
return cy.getByTestId('wizard-mapper-entities-container').find('label[for="mapper-sources"]')
58+
},
59+
60+
get moreInfo() {
61+
return cy.getByTestId('wizard-mapper-entities-container').find('[data-testid="more-info-trigger"]')
62+
},
63+
64+
get select() {
65+
return cy.getByTestId('wizard-mapper-entities-container').find('#wizard-mapper-sources')
66+
},
67+
68+
get values() {
69+
return cy.getByTestId('wizard-mapper-entities-container').find('[data-testid="multi-selected-value"]')
70+
}, //react-select-mapper-placeholder
71+
72+
get helperText() {
73+
return cy.getByTestId('wizard-mapper-entities-container').find('#react-select-sources-helper')
74+
},
75+
}
76+
}
77+
}
78+
79+
export const assetMappingWizard = new AssetMappingWizardForm()

0 commit comments

Comments
 (0)