Skip to content

Commit 91d6c0b

Browse files
committed
Vue 3: fix & improve log view tests
1 parent 28ecb59 commit 91d6c0b

File tree

3 files changed

+168
-94
lines changed

3 files changed

+168
-94
lines changed

src/views/Log.vue

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
3333
<v-row dense>
3434
<v-col>
3535
<v-btn-toggle
36-
class="job-workflow-toggle"
3736
v-model="jobLog"
3837
divided
3938
mandatory
4039
variant="outlined"
4140
color="primary"
4241
>
43-
<v-btn>Workflow</v-btn>
44-
<v-btn>Job</v-btn>
42+
<v-btn data-cy="workflow-toggle">Workflow</v-btn>
43+
<v-btn data-cy="job-toggle">Job</v-btn>
4544
</v-btn-toggle>
4645
<ViewToolbar
4746
:groups="groups"
@@ -55,23 +54,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
5554
<v-col cols="8">
5655
<v-text-field
5756
v-if="jobLog"
58-
class="flex-grow-1 flex-column job-id-input"
57+
data-cy="job-id-input"
58+
class="flex-grow-1 flex-column"
5959
v-model="relativeID"
6060
placeholder="cycle/task/job"
6161
hide-details
6262
clearable
6363
/>
6464
<v-text-field
6565
v-else
66-
class="workflow-id-input"
66+
data-cy="workflow-id-input"
6767
v-model="workflowId"
6868
disabled
6969
hide-details
7070
/>
7171
</v-col>
7272
<v-col cols="4">
7373
<v-select
74-
class="file-input"
74+
data-cy="file-input"
7575
:label="fileLabel"
7676
:disabled="fileDisabled"
7777
:items="logFiles"
@@ -90,38 +90,33 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
9090
style="white-space: pre"
9191
>
9292
<v-chip
93-
class="connected-icon"
94-
v-if="results.connected"
95-
color="green"
93+
data-cy="connected-icon"
9694
variant="outlined"
97-
:prepend-icon="$options.icons.mdiPowerPlug"
9895
class="flex-shrink-0"
96+
v-bind="results.connected ? {
97+
color: 'green',
98+
prependIcon: $options.icons.mdiPowerPlug,
99+
} : {
100+
color: 'red',
101+
prependIcon: $options.icons.mdiPowerPlugOff,
102+
onClick: updateQuery
103+
}"
99104
>
100-
Connected
101-
</v-chip>
102-
<v-chip
103-
class="disconnected-icon"
104-
v-else
105-
color="red"
106-
@click="updateQuery"
107-
variant="outlined"
108-
:prepend-icon="$options.icons.mdiPowerPlugOff"
109-
class="flex-shrink-0"
110-
>
111-
Reconnect
105+
{{ results.connected ? 'Connected' : 'Reconnect' }}
112106
</v-chip>
113107
<span
114-
class="log-path"
108+
data-cy="log-path"
115109
style="padding-left: 0.5em; color: rgb(150,150,150);"
116-
>{{ results.path }}</span>
110+
>
111+
{{ results.path }}
112+
</span>
117113
</v-col>
118114
</v-row>
119115

120116
<!-- the log file viewer -->
121117
<v-row>
122118
<v-col>
123-
<!-- TODO: replace v-progress-linear with v-skeleton-loader when
124-
the latter is added to Vuetify 3.
119+
<!-- TODO: replace v-progress-linear with v-skeleton-loader
125120
https://github.com/cylc/cylc-ui/issues/1272 -->
126121
<!-- <v-skeleton-loader
127122
v-if="id && file && !results.path"
@@ -133,8 +128,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
133128
indeterminate
134129
/>
135130
<log-component
136-
class="log-viewer"
137131
v-else
132+
data-cy="log-viewer"
138133
:logs="results.lines"
139134
:timestamps="timestamps"
140135
/>

tests/e2e/specs/log.cy.js

Lines changed: 45 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -16,92 +16,70 @@
1616
*/
1717

1818
describe('Log View', () => {
19-
it('works', () => {
20-
cy
21-
.visit('/#/log/one')
19+
beforeEach(() => {
20+
cy.visit('/#/log/one')
21+
})
2222

23-
// the workflow ID should be filled in
24-
.get('.workflow-id-input')
25-
.find('input')
23+
it('fills in inputs', () => {
24+
// the workflow ID should be filled in
25+
cy.get('.c-log [data-cy=workflow-id-input]')
2626
.should('be.visible')
27-
.should('have.attr', 'disabled', 'disabled')
28-
.should(($input) => {
29-
expect($input.val()).to.equal('~user/one')
30-
})
31-
32-
// the job log files list should have been populated by the query
33-
.get('.c-log')
34-
.should(($el) => {
35-
expect($el[0].__vue__.logFiles).to.have.length(3)
36-
})
27+
.find('input')
28+
.should('have.value', '~user/one')
29+
.should('have.attr', 'disabled')
3730

38-
// the job.out file should have been selected
39-
.should(($el) => {
40-
expect($el[0].__vue__.file).to.equal('job.out')
41-
})
42-
.get('.file-input')
31+
// the job.out file should have been selected
32+
cy.get('.c-log [data-cy=file-input]')
4333
.should('be.visible')
44-
.should('not.have.attr', 'disabled', 'disabled')
34+
.find('input')
35+
.should('have.value', 'job.out')
36+
.should('not.have.attr', 'disabled')
4537

46-
// the subscription should have been issued
47-
.get('.c-log')
48-
.should(($el) => {
49-
expect($el[0].__vue__.query.variables.id).to.equal('~user/one')
50-
expect($el[0].__vue__.query.variables.file).to.equal('job.out')
51-
})
38+
// the job log files list should have been populated by the query
39+
cy.get('.c-log [data-cy=file-input]')
40+
.click()
41+
.get('.v-select__content .v-list-item')
42+
.contains('job.out')
43+
.parents('[role=listbox]')
44+
.children()
45+
.should('have.length', 3)
46+
})
5247

53-
// the log file should have been loaded into the viewer
54-
.get('.log-viewer')
48+
it('loads the log file', () => {
49+
// the log file should have been loaded into the viewer
50+
cy.get('[data-cy=log-viewer]')
5551
.contains('one\ntwo\nthree\nfour\nfive')
5652

57-
// the file path should be displayed
58-
.get('.log-path')
53+
// the file path should be displayed
54+
cy.get('[data-cy=log-path]')
5955
.should('be.visible')
6056
.contains('my-host:')
6157
.contains('job.out')
6258

63-
// the connected icon should be visible
64-
.get('.connected-icon')
59+
// the connected icon should be visible
60+
cy.get('[data-cy=connected-icon]')
6561
.should('be.visible')
62+
})
6663

67-
// toggle the mode from workflow => job
68-
.get('.job-workflow-toggle > :nth-child(2)')
69-
.click({ force: true })
70-
.get('.c-log')
71-
.should(($el) => {
72-
// the old file name should have been wiped
73-
expect($el[0].__vue__.file).to.equal(null)
74-
// the old query should have been wiped (this triggers unsubscribe)
75-
expect($el[0].__vue__.query).to.equal(null)
76-
// the old log file lines should have been wiped
77-
expect($el[0].__vue__.results.lines).to.deep.equal([])
78-
})
64+
it('switches from workflow -> job log', () => {
65+
cy.get('[data-cy=job-toggle]')
66+
.click()
67+
// the old log file lines should have been wiped
68+
.get('[data-cy=log-viewer] > pre')
69+
.should('be.empty')
7970

80-
// fill in a cycle point (invalid)
81-
.get('.job-id-input')
71+
// fill in a cycle point (incomplete)
72+
cy.get('[data-cy=job-id-input]')
8273
.find('input')
83-
.type('1')
84-
.get('.c-log')
85-
.should(($el) => {
86-
// the id should not have been updated
87-
expect($el[0].__vue__.id).to.equal(null)
88-
// and no query should have been issued
89-
expect($el[0].__vue__.query).to.equal(null)
90-
})
91-
74+
.type('1/')
75+
.get('[data-cy=log-viewer] > pre')
76+
.should('be.empty')
9277
// fill in a task (valid)
93-
.get('.job-id-input')
78+
.get('[data-cy=job-id-input]')
9479
.find('input')
95-
.type('/a')
96-
.get('.c-log')
97-
.should(($el) => {
98-
// a new query should have been issued
99-
expect($el[0].__vue__.query.variables.id).to.equal('~user/one//1/a')
100-
expect($el[0].__vue__.query.variables.file).to.equal('job.out')
101-
})
102-
80+
.type('a')
10381
// the new log file should have been loaded
104-
.get('.log-viewer')
82+
.get('[data-cy=log-viewer] > pre')
10583
.contains('one\ntwo\nthree\nfour\nfive')
10684
})
10785
})

tests/unit/views/log.vue.spec.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* Copyright (C) NIWA & British Crown (Met Office) & Contributors.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
import { nextTick } from 'vue'
19+
import { mount } from '@vue/test-utils'
20+
import { createStore } from 'vuex'
21+
import storeOptions from '@/store/options'
22+
import { createVuetify } from 'vuetify'
23+
import sinon from 'sinon'
24+
import Log from '@/views/Log.vue'
25+
import WorkflowService from '@/services/workflow.service'
26+
import User from '@/model/User.model'
27+
import { Tokens } from '@/utils/uid'
28+
29+
describe('Log view', () => {
30+
const owner = 'svimes'
31+
const workflowName = 'thud'
32+
const workflowID = `~${owner}/${workflowName}`
33+
const initialFile = 'koom-valley.log'
34+
35+
const vuetify = createVuetify()
36+
let $workflowService, store
37+
38+
const mountFunction = (options) => mount(Log, {
39+
global: {
40+
plugins: [vuetify, store],
41+
mocks: { $workflowService },
42+
},
43+
props: {
44+
workflowName,
45+
initialOptions: {
46+
tokens: new Tokens(workflowID),
47+
file: initialFile,
48+
},
49+
},
50+
...options
51+
})
52+
53+
beforeEach(() => {
54+
store = createStore(storeOptions)
55+
store.commit(
56+
'user/SET_USER',
57+
new User('cylc', [], new Date(), true, 'localhost', owner)
58+
)
59+
$workflowService = sinon.createStubInstance(WorkflowService)
60+
})
61+
62+
it('issues the subscription', async () => {
63+
const wrapper = mountFunction()
64+
expect(wrapper.vm.jobLog).toEqual(0)
65+
expect(wrapper.vm.query.variables).toMatchObject({
66+
id: workflowID,
67+
file: initialFile,
68+
})
69+
expect(wrapper.vm.file).toEqual(initialFile)
70+
// switch workflow -> job log
71+
wrapper.vm.jobLog = 1
72+
await nextTick()
73+
// old file & log lines should be wiped
74+
expect(wrapper.vm.file).toBe(null)
75+
expect(wrapper.vm.results.lines).toEqual([])
76+
// should have unsubscribed
77+
expect(wrapper.vm.$workflowService.unsubscribe.calledOnce).toBe(true)
78+
})
79+
80+
it('does not issue subscription for incomplete task ID', async () => {
81+
const wrapper = mountFunction({
82+
data: () => ({
83+
jobLog: 1,
84+
relativeID: '2000', // cycle point only is invalid
85+
})
86+
})
87+
expect(wrapper.vm.id).toBe(null)
88+
expect(wrapper.vm.query).toBe(null)
89+
// type in complete task ID
90+
wrapper.vm.relativeID += '/bashfullsson'
91+
wrapper.vm.file = 'job.out'
92+
await nextTick()
93+
const expectedID = `${workflowID}//2000/bashfullsson`
94+
expect(wrapper.vm.id).toEqual(expectedID)
95+
// query issued
96+
expect(wrapper.vm.query.variables).toMatchObject({
97+
id: expectedID,
98+
file: 'job.out',
99+
})
100+
})
101+
})

0 commit comments

Comments
 (0)