glib-web 5.0.7 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/gen-cypress-test.md +101 -0
- package/.github/dependabot.yml +24 -12
- package/.nycrc.json +4 -0
- package/AGENTS.md +1 -0
- package/README.md +4 -0
- package/actions/logics/set.js +5 -0
- package/actions/panels/scrollTo.js +2 -2
- package/actions/timeouts/set.js +1 -1
- package/app.scss +19 -0
- package/app.vue +1 -1
- package/components/charts/series.js +2 -2
- package/components/composable/gmap.js +1 -1
- package/components/composable/upload.js +1 -4
- package/components/composable/upload_nothing.js +1 -1
- package/components/fields/_buttonDate.vue +2 -2
- package/components/fields/_patternText.vue +2 -8
- package/components/fields/_select.vue +10 -8
- package/components/fields/_selectItemDefault.vue +1 -3
- package/components/fields/_selectItemWithIcon.vue +1 -3
- package/components/fields/_selectItemWithImage.vue +1 -3
- package/components/fields/clipboardUpload.vue +115 -0
- package/components/fields/dynamicGroup2.vue +1 -1
- package/components/fields/multiUpload.vue +12 -19
- package/components/fields/placeholderUpload.vue +8 -4
- package/components/fields/radioGroup.vue +0 -7
- package/components/fields/sign.vue +1 -1
- package/components/fields/text.vue +1 -9
- package/components/fields/upload.vue +4 -0
- package/components/mixins/longClick.js +1 -1
- package/components/panels/bulkEdit2.vue +1 -1
- package/components/panels/flow.vue +19 -10
- package/components/panels/list.vue +1 -1
- package/components/popover.vue +1 -1
- package/components/responsive.vue +1 -1
- package/cypress/e2e/glib-web/dialog.cy.js +0 -1
- package/cypress/e2e/glib-web/dirtyState.cy.js +37 -37
- package/cypress/e2e/glib-web/fieldsDateTime.cy.js +46 -3
- package/cypress/e2e/glib-web/fieldsRadio.cy.js +65 -0
- package/cypress/e2e/glib-web/fieldsSelect.cy.js +116 -76
- package/cypress/e2e/glib-web/fieldsText.cy.js +83 -0
- package/cypress/e2e/glib-web/fieldsUpload.cy.js +230 -120
- package/cypress/e2e/glib-web/image.cy.js +62 -33
- package/cypress/e2e/glib-web/switch.cy.js +13 -0
- package/cypress/e2e/glib-web/tabBar.cy.js +23 -0
- package/cypress/fixtures/document.pdf +12 -0
- package/cypress/fixtures/large.png +0 -0
- package/cypress/fixtures/upload.png +0 -0
- package/cypress/helper.js +13 -1
- package/cypress/support/component.js +1 -1
- package/cypress/support/e2e.js +20 -13
- package/doc/dependabot.md +22 -0
- package/eslint-rules/index.js +6 -6
- package/nav/dialog.vue +1 -1
- package/package.json +18 -16
- package/templates/_menu.vue +2 -2
- package/cypress/component/inputUpload.cy.js +0 -103
- package/cypress/component/multiUpload.cy.js +0 -107
- package/cypress/component/placeholderUpload.cy.js +0 -91
|
@@ -1,57 +1,30 @@
|
|
|
1
|
-
import { testPageUrl } from "../../helper.js"
|
|
1
|
+
import { testPageUrl, withComponent, withComponentThen } from "../../helper.js";
|
|
2
2
|
|
|
3
|
-
const url = testPageUrl('fields_upload')
|
|
4
|
-
|
|
5
|
-
const getRealComponent = (win, component) => {
|
|
6
|
-
const { isObject, isNotNull } = win.Utils.type
|
|
7
|
-
let realComp = component
|
|
8
|
-
|
|
9
|
-
while (isObject(realComp) && isNotNull(realComp.$refs?.delegate)) {
|
|
10
|
-
realComp = realComp.$refs.delegate
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return realComp
|
|
14
|
-
}
|
|
3
|
+
const url = testPageUrl('fields_upload');
|
|
15
4
|
|
|
16
5
|
describe('fields_upload', () => {
|
|
17
|
-
it('invokes upload triggers and resets', () => {
|
|
18
|
-
cy.visit(url)
|
|
19
|
-
|
|
20
|
-
cy.window().then((win) => {
|
|
21
|
-
const basicComp = getRealComponent(win, win.GLib.component.findById('fields_upload_basic'))
|
|
22
|
-
const filesComp = getRealComponent(win, win.GLib.component.findById('fields_upload_files'))
|
|
23
|
-
|
|
24
|
-
cy.wrap(basicComp).should('exist')
|
|
25
|
-
cy.wrap(filesComp).should('exist')
|
|
26
6
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
cy.spy(filesComp, 'trigger').as('filesTrigger')
|
|
30
|
-
})
|
|
7
|
+
it('resets the avatar placeholderView field value', () => {
|
|
8
|
+
cy.visit(url);
|
|
31
9
|
|
|
32
|
-
cy.
|
|
33
|
-
cy.get('@basicTrigger').should('have.been.calledOnce')
|
|
10
|
+
withComponent('fields_upload_avatar', (comp) => cy.spy(comp, 'reset').as('avatarReset'));
|
|
34
11
|
|
|
35
|
-
cy.contains(
|
|
36
|
-
cy.get('@
|
|
12
|
+
cy.get('.v-btn').filter(':contains("Reset")').eq(1).click();
|
|
13
|
+
cy.get('@avatarReset').should('have.been.calledOnce');
|
|
37
14
|
|
|
38
|
-
cy.contains('
|
|
39
|
-
cy.get('
|
|
40
|
-
|
|
15
|
+
cy.contains('submit').click();
|
|
16
|
+
cy.get('.unformatted').should('contain.text', 'Method: POST').and('not.contain.text', '"file_avatar": "test"');
|
|
17
|
+
cy.get('.v-dialog').contains('OK').click();
|
|
18
|
+
});
|
|
41
19
|
|
|
42
20
|
it('handles multi progress view drag and drop uploads', () => {
|
|
43
|
-
const htmlUploadUrl = 'http://localhost:3000/glib/json_ui_garage?path=forms%2Ffile_upload_new&mode=html'
|
|
44
|
-
let uploadIndex = 0
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const pngFile = {
|
|
48
|
-
contents: Cypress.Buffer.from(pngBase64, 'base64'),
|
|
49
|
-
fileName: 'upload.png',
|
|
50
|
-
mimeType: 'image/png'
|
|
51
|
-
}
|
|
21
|
+
const htmlUploadUrl = 'http://localhost:3000/glib/json_ui_garage?path=forms%2Ffile_upload_new&mode=html';
|
|
22
|
+
let uploadIndex = 0;
|
|
23
|
+
|
|
24
|
+
cy.fixture('upload.png', null).as('pngContents');
|
|
52
25
|
|
|
53
26
|
cy.intercept('POST', /\/(glib\/glib_direct_uploads|rails\/active_storage\/direct_uploads)$/, (req) => {
|
|
54
|
-
uploadIndex += 1
|
|
27
|
+
uploadIndex += 1;
|
|
55
28
|
req.reply({
|
|
56
29
|
statusCode: 200,
|
|
57
30
|
body: {
|
|
@@ -63,97 +36,234 @@ describe('fields_upload', () => {
|
|
|
63
36
|
}
|
|
64
37
|
}
|
|
65
38
|
}
|
|
66
|
-
})
|
|
67
|
-
}).as('directUploadCreate')
|
|
39
|
+
});
|
|
40
|
+
}).as('directUploadCreate');
|
|
68
41
|
|
|
69
42
|
cy.intercept('PUT', 'https://example.com/rails/active_storage/blobs/upload', {
|
|
70
43
|
statusCode: 200
|
|
71
|
-
}).as('directUploadStore')
|
|
72
|
-
|
|
73
|
-
cy.visit(url)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
cy.get('@dropzone').
|
|
90
|
-
cy.get('@
|
|
91
|
-
|
|
92
|
-
cy.get('@
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
cy.wait('@directUploadCreate')
|
|
104
|
-
cy.wait('@directUploadStore')
|
|
44
|
+
}).as('directUploadStore');
|
|
45
|
+
|
|
46
|
+
cy.visit(url);
|
|
47
|
+
|
|
48
|
+
withComponentThen('fields_upload_multiprogress', (comp) => {
|
|
49
|
+
expect(comp.$el, 'multi progress element').to.exist;
|
|
50
|
+
expect(comp.$el.querySelector('.gdrop-file'), 'multi progress dropzone').to.exist;
|
|
51
|
+
cy.wrap(comp.$el).as('multiRoot');
|
|
52
|
+
cy.wrap(comp.$el.querySelector('.gdrop-file')).as('dropzone');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
cy.get('@dropzone').should('have.class', 'border-[2px]');
|
|
56
|
+
cy.get('@dropzone').trigger('drag');
|
|
57
|
+
cy.get('@dropzone').trigger('dragover');
|
|
58
|
+
cy.get('@dropzone').should('have.class', 'border-[4px]').and('not.have.class', 'border-[2px]');
|
|
59
|
+
cy.get('@dropzone').trigger('dragleave');
|
|
60
|
+
cy.get('@dropzone').should('have.class', 'border-[2px]');
|
|
61
|
+
|
|
62
|
+
cy.get('@dropzone').click({ force: true });
|
|
63
|
+
cy.get('@multiRoot').find('input[type="file"]').should('exist');
|
|
64
|
+
|
|
65
|
+
cy.get('@pngContents').then((contents) => {
|
|
66
|
+
cy.get('@dropzone').selectFile(
|
|
67
|
+
{ contents, fileName: 'upload.png', mimeType: 'image/png' },
|
|
68
|
+
{ action: 'drag-drop' }
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
cy.wait('@directUploadCreate');
|
|
73
|
+
cy.wait('@directUploadStore');
|
|
105
74
|
cy.get('@multiRoot')
|
|
106
75
|
.find('input[type="hidden"][name="user[file_input_multiprogress][]"]')
|
|
107
|
-
.should('have.length', 1)
|
|
76
|
+
.should('have.length', 1);
|
|
108
77
|
|
|
109
|
-
cy.get('@
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
78
|
+
cy.get('@pngContents').then((contents) => {
|
|
79
|
+
cy.get('@dropzone').selectFile(
|
|
80
|
+
{ contents, fileName: 'second.png', mimeType: 'image/png' },
|
|
81
|
+
{ action: 'drag-drop' }
|
|
82
|
+
);
|
|
83
|
+
});
|
|
113
84
|
|
|
114
|
-
cy.wait('@directUploadCreate')
|
|
115
|
-
cy.wait('@directUploadStore')
|
|
85
|
+
cy.wait('@directUploadCreate');
|
|
86
|
+
cy.wait('@directUploadStore');
|
|
116
87
|
|
|
117
|
-
cy.get('@multiRoot').find('.guploaded-file .file-container').should('have.length', 2)
|
|
88
|
+
cy.get('@multiRoot').find('.guploaded-file .file-container').should('have.length', 2);
|
|
118
89
|
cy.get('@multiRoot')
|
|
119
90
|
.find('input[type="hidden"][name="user[file_input_multiprogress][]"]')
|
|
120
|
-
.should('have.length', 2)
|
|
121
|
-
cy.get('@multiRoot').find('.guploaded-file .close-btn').first().click()
|
|
122
|
-
cy.get('@multiRoot').find('.guploaded-file .file-container').should('have.length', 1)
|
|
91
|
+
.should('have.length', 2);
|
|
92
|
+
cy.get('@multiRoot').find('.guploaded-file .close-btn').first().click();
|
|
93
|
+
cy.get('@multiRoot').find('.guploaded-file .file-container').should('have.length', 1);
|
|
123
94
|
cy.get('@multiRoot')
|
|
124
95
|
.find('input[type="hidden"][name="user[file_input_multiprogress][]"]')
|
|
125
|
-
.should('have.length', 1)
|
|
126
|
-
|
|
127
|
-
cy.visit(htmlUploadUrl)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
{ action: 'drag-drop' }
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
cy.get('@htmlMultiRoot').find('.guploaded-file .file-container').should('have.length', 1)
|
|
96
|
+
.should('have.length', 1);
|
|
97
|
+
|
|
98
|
+
cy.visit(htmlUploadUrl);
|
|
99
|
+
|
|
100
|
+
withComponentThen('mp1', (comp) => {
|
|
101
|
+
expect(comp.$el, 'html multi progress element').to.exist;
|
|
102
|
+
expect(comp.$el.querySelector('.gdrop-file'), 'html multi progress dropzone').to.exist;
|
|
103
|
+
cy.wrap(comp.$el).as('htmlMultiRoot');
|
|
104
|
+
cy.wrap(comp.$el.querySelector('.gdrop-file')).as('htmlDropzone');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
cy.get('@pngContents').then((contents) => {
|
|
108
|
+
cy.get('@htmlDropzone').selectFile(
|
|
109
|
+
{ contents, fileName: 'html-mode.png', mimeType: 'image/png' },
|
|
110
|
+
{ action: 'drag-drop' }
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
cy.get('@htmlMultiRoot').find('.guploaded-file .file-container').should('have.length', 1);
|
|
148
115
|
cy.get('@htmlMultiRoot')
|
|
149
116
|
.find('input[type="hidden"][name="user[file_multiprogress1][]"]')
|
|
150
|
-
.should('not.exist')
|
|
151
|
-
})
|
|
117
|
+
.should('not.exist');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('rejects files with an invalid type in multi progress view', () => {
|
|
121
|
+
cy.fixture('document.pdf', null).as('pdfContents');
|
|
122
|
+
|
|
123
|
+
cy.visit(url);
|
|
124
|
+
|
|
125
|
+
withComponentThen('fields_upload_multiprogress', (comp) => {
|
|
126
|
+
expect(comp.$el.querySelector('.gdrop-file'), 'dropzone').to.exist;
|
|
127
|
+
cy.wrap(comp.$el).as('multiRoot');
|
|
128
|
+
cy.wrap(comp.$el.querySelector('.gdrop-file')).as('dropzone');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
cy.get('@pdfContents').then((contents) => {
|
|
132
|
+
cy.get('@dropzone').selectFile(
|
|
133
|
+
{ contents, fileName: 'document.pdf', mimeType: 'application/pdf' },
|
|
134
|
+
{ action: 'drag-drop' }
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
cy.get('@multiRoot').find('.guploaded-file').should('not.exist');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('rejects files that exceed the maximum file size in multi progress view', () => {
|
|
141
|
+
// maxFileSize: 10 in the test page → 10 * 1000 = 10,000 bytes limit
|
|
142
|
+
cy.fixture('large.png', null).as('largeContents');
|
|
143
|
+
|
|
144
|
+
cy.visit(url);
|
|
145
|
+
|
|
146
|
+
withComponentThen('fields_upload_multiprogress', (comp) => {
|
|
147
|
+
expect(comp.$el.querySelector('.gdrop-file'), 'dropzone').to.exist;
|
|
148
|
+
cy.wrap(comp.$el).as('multiRoot');
|
|
149
|
+
cy.wrap(comp.$el.querySelector('.gdrop-file')).as('dropzone');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
cy.get('@largeContents').then((contents) => {
|
|
153
|
+
cy.get('@dropzone').selectFile(
|
|
154
|
+
{ contents, fileName: 'large.png', mimeType: 'image/png' },
|
|
155
|
+
{ action: 'drag-drop' }
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
cy.get('@multiRoot').find('.guploaded-file').should('not.exist');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('rejects uploads that exceed the maximum file count in multi progress view', () => {
|
|
162
|
+
let uploadIndex = 0;
|
|
163
|
+
|
|
164
|
+
cy.fixture('upload.png', null).as('pngContents');
|
|
165
|
+
|
|
166
|
+
cy.intercept('POST', /\/(glib\/glib_direct_uploads|rails\/active_storage\/direct_uploads)$/, (req) => {
|
|
167
|
+
uploadIndex += 1;
|
|
168
|
+
req.reply({
|
|
169
|
+
statusCode: 200,
|
|
170
|
+
body: {
|
|
171
|
+
signed_id: `signed-${uploadIndex}`,
|
|
172
|
+
direct_upload: {
|
|
173
|
+
url: 'https://example.com/rails/active_storage/blobs/upload',
|
|
174
|
+
headers: { 'Content-Type': 'text/plain' }
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}).as('directUploadCreate');
|
|
179
|
+
|
|
180
|
+
cy.intercept('PUT', 'https://example.com/rails/active_storage/blobs/upload', { statusCode: 200 }).as('directUploadStore');
|
|
181
|
+
|
|
182
|
+
cy.visit(url);
|
|
183
|
+
|
|
184
|
+
withComponentThen('fields_upload_multiprogress', (comp) => {
|
|
185
|
+
expect(comp.$el.querySelector('.gdrop-file'), 'dropzone').to.exist;
|
|
186
|
+
cy.wrap(comp.$el).as('multiRoot');
|
|
187
|
+
cy.wrap(comp.$el.querySelector('.gdrop-file')).as('dropzone');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Upload 2 files to reach maxFileLength: 2
|
|
191
|
+
cy.get('@pngContents').then((contents) => {
|
|
192
|
+
cy.get('@dropzone').selectFile(
|
|
193
|
+
{ contents, fileName: 'first.png', mimeType: 'image/png' },
|
|
194
|
+
{ action: 'drag-drop' }
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
cy.wait('@directUploadCreate');
|
|
198
|
+
cy.wait('@directUploadStore');
|
|
199
|
+
cy.get('@pngContents').then((contents) => {
|
|
200
|
+
cy.get('@dropzone').selectFile(
|
|
201
|
+
{ contents, fileName: 'second.png', mimeType: 'image/png' },
|
|
202
|
+
{ action: 'drag-drop' }
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
cy.wait('@directUploadCreate');
|
|
206
|
+
cy.wait('@directUploadStore');
|
|
207
|
+
cy.get('@multiRoot').find('.guploaded-file .file-container').should('have.length', 2);
|
|
208
|
+
|
|
209
|
+
// A 3rd file should be rejected — container count stays at 2
|
|
210
|
+
cy.get('@pngContents').then((contents) => {
|
|
211
|
+
cy.get('@dropzone').selectFile(
|
|
212
|
+
{ contents, fileName: 'third.png', mimeType: 'image/png' },
|
|
213
|
+
{ action: 'drag-drop' }
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
cy.get('@multiRoot').find('.guploaded-file .file-container').should('have.length', 2);
|
|
217
|
+
});
|
|
152
218
|
|
|
153
219
|
it('submits the form', () => {
|
|
154
|
-
cy.visit(url)
|
|
220
|
+
cy.visit(url);
|
|
221
|
+
|
|
222
|
+
cy.contains('submit').click();
|
|
223
|
+
cy.get('.unformatted').should('contain.text', 'Method: POST');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('pastes an image from clipboard and shows filename', () => {
|
|
227
|
+
let uploadIndex = 0;
|
|
228
|
+
|
|
229
|
+
cy.intercept('POST', /\/(glib\/glib_direct_uploads|rails\/active_storage\/direct_uploads)$/, (req) => {
|
|
230
|
+
uploadIndex += 1;
|
|
231
|
+
req.reply({
|
|
232
|
+
statusCode: 200,
|
|
233
|
+
body: {
|
|
234
|
+
signed_id: `signed-clip-${uploadIndex}`,
|
|
235
|
+
direct_upload: {
|
|
236
|
+
url: 'https://example.com/rails/active_storage/blobs/upload',
|
|
237
|
+
headers: { 'Content-Type': 'text/plain' }
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}).as('directUploadCreate');
|
|
242
|
+
|
|
243
|
+
cy.intercept('PUT', 'https://example.com/rails/active_storage/blobs/upload', {
|
|
244
|
+
statusCode: 200
|
|
245
|
+
}).as('directUploadStore');
|
|
246
|
+
|
|
247
|
+
cy.visit(url);
|
|
248
|
+
|
|
249
|
+
withComponentThen('fields_upload_clipboard', (comp) => {
|
|
250
|
+
cy.wrap(comp.$el).as('clipRoot');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
cy.window().then((win) => {
|
|
254
|
+
const mockBlob = new win.Blob(['fake-png'], { type: 'image/png' });
|
|
255
|
+
cy.stub(win.navigator.clipboard, 'read').resolves([
|
|
256
|
+
{ types: ['image/png'], getType: () => Promise.resolve(mockBlob) }
|
|
257
|
+
]);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
cy.get('@clipRoot').find('.v-input__append i').click();
|
|
261
|
+
cy.wait('@directUploadCreate');
|
|
262
|
+
cy.wait('@directUploadStore');
|
|
155
263
|
|
|
156
|
-
cy.
|
|
157
|
-
cy.get('
|
|
158
|
-
|
|
159
|
-
|
|
264
|
+
cy.get('@clipRoot').find('input').should('have.value', 'clipboard.png');
|
|
265
|
+
cy.get('@clipRoot')
|
|
266
|
+
.find('input[type="hidden"][name="user[file_clipboard]"]')
|
|
267
|
+
.should('have.value', 'signed-clip-1');
|
|
268
|
+
});
|
|
269
|
+
});
|
|
@@ -1,53 +1,82 @@
|
|
|
1
|
-
import { testPageUrl } from "../../helper.js"
|
|
1
|
+
import { testPageUrl } from "../../helper.js";
|
|
2
2
|
|
|
3
|
-
const url = testPageUrl('image')
|
|
4
|
-
const imageSelector = '#image_main'
|
|
3
|
+
const url = testPageUrl('image');
|
|
4
|
+
const imageSelector = '#image_main';
|
|
5
5
|
|
|
6
6
|
const getImage = () => {
|
|
7
7
|
return cy.get(imageSelector).then(($el) => {
|
|
8
|
-
const image = $el.is('img') ? $el : $el.find('img')
|
|
9
|
-
expect(image.length).to.be.greaterThan(0)
|
|
10
|
-
return cy.wrap(image)
|
|
11
|
-
})
|
|
12
|
-
}
|
|
8
|
+
const image = $el.is('img') ? $el : $el.find('img');
|
|
9
|
+
expect(image.length).to.be.greaterThan(0);
|
|
10
|
+
return cy.wrap(image);
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
13
|
|
|
14
14
|
describe('image', () => {
|
|
15
15
|
it('updates size and fit settings', () => {
|
|
16
|
-
cy.visit(url)
|
|
16
|
+
cy.visit(url);
|
|
17
17
|
|
|
18
|
-
cy.contains('Default').click()
|
|
19
|
-
getImage().should('have.css', 'object-fit', 'contain')
|
|
18
|
+
cy.contains('Default').click();
|
|
19
|
+
getImage().should('have.css', 'object-fit', 'contain');
|
|
20
20
|
|
|
21
|
-
cy.contains('Crop square').click()
|
|
22
|
-
getImage().should('have.css', 'object-fit', 'cover')
|
|
21
|
+
cy.contains('Crop square').click();
|
|
22
|
+
getImage().should('have.css', 'object-fit', 'cover');
|
|
23
23
|
getImage().should(($image) => {
|
|
24
|
-
expect($image.attr('width')).to.eq('160px')
|
|
25
|
-
})
|
|
24
|
+
expect($image.attr('width')).to.eq('160px');
|
|
25
|
+
});
|
|
26
26
|
|
|
27
|
-
cy.contains('Full width').click()
|
|
28
|
-
getImage().should('have.css', 'object-fit', 'contain')
|
|
27
|
+
cy.contains('Full width').click();
|
|
28
|
+
getImage().should('have.css', 'object-fit', 'contain');
|
|
29
29
|
|
|
30
|
-
cy.contains('Tall crop').click()
|
|
31
|
-
getImage().should('have.css', 'object-fit', 'cover')
|
|
32
|
-
})
|
|
30
|
+
cy.contains('Tall crop').click();
|
|
31
|
+
getImage().should('have.css', 'object-fit', 'cover');
|
|
32
|
+
});
|
|
33
33
|
|
|
34
34
|
it('toggles badges', () => {
|
|
35
|
-
cy.visit(url)
|
|
35
|
+
cy.visit(url);
|
|
36
36
|
|
|
37
|
-
cy.contains('Show badge').click()
|
|
38
|
-
cy.get('#image_main .v-badge__badge').should('contain.text', '3')
|
|
39
|
-
})
|
|
37
|
+
cy.contains('Show badge').click();
|
|
38
|
+
cy.get('#image_main .v-badge__badge').should('contain.text', '3');
|
|
39
|
+
});
|
|
40
40
|
|
|
41
41
|
it('handles tiny thumbnails', () => {
|
|
42
|
-
cy.visit(url)
|
|
42
|
+
cy.visit(url);
|
|
43
43
|
|
|
44
|
-
cy.contains('Tiny thumbnail').click()
|
|
45
|
-
getImage().should('have.css', 'object-fit', 'cover')
|
|
44
|
+
cy.contains('Tiny thumbnail').click();
|
|
45
|
+
getImage().should('have.css', 'object-fit', 'cover');
|
|
46
46
|
getImage().should(($image) => {
|
|
47
|
-
expect($image.attr('width')).to.eq('64px')
|
|
48
|
-
})
|
|
47
|
+
expect($image.attr('width')).to.eq('64px');
|
|
48
|
+
});
|
|
49
49
|
|
|
50
|
-
cy.contains('Restore size').click()
|
|
51
|
-
getImage().should('have.css', 'object-fit', 'contain')
|
|
52
|
-
})
|
|
53
|
-
|
|
50
|
+
cy.contains('Restore size').click();
|
|
51
|
+
getImage().should('have.css', 'object-fit', 'contain');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('fits by height only (clip)', () => {
|
|
55
|
+
cy.visit(url);
|
|
56
|
+
|
|
57
|
+
cy.contains('Height clip').click();
|
|
58
|
+
getImage().should('have.css', 'object-fit', 'contain');
|
|
59
|
+
getImage().should(($image) => {
|
|
60
|
+
expect($image.attr('height')).to.eq('200px');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('fits by height only (crop)', () => {
|
|
65
|
+
cy.visit(url);
|
|
66
|
+
|
|
67
|
+
cy.contains('Height crop').click();
|
|
68
|
+
getImage().should('have.css', 'object-fit', 'cover');
|
|
69
|
+
getImage().should(($image) => {
|
|
70
|
+
expect($image.attr('height')).to.eq('160px');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('handles image onClick', () => {
|
|
75
|
+
cy.visit(url);
|
|
76
|
+
|
|
77
|
+
cy.on('window:alert', (message) => {
|
|
78
|
+
expect(message).to.include('Image clicked');
|
|
79
|
+
});
|
|
80
|
+
getImage().click();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { testPageUrl } from "../../helper.js";
|
|
2
|
+
|
|
3
|
+
const url = testPageUrl('switch');
|
|
4
|
+
|
|
5
|
+
describe('switch', () => {
|
|
6
|
+
it('toggles push notifications on from default off state', () => {
|
|
7
|
+
cy.visit(url);
|
|
8
|
+
|
|
9
|
+
cy.contains('Push notifications').click();
|
|
10
|
+
|
|
11
|
+
cy.contains('Push enabled').should('be.visible');
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { testPageUrl } from "../../helper.js";
|
|
2
|
+
|
|
3
|
+
const url = testPageUrl('tabBar');
|
|
4
|
+
|
|
5
|
+
describe('tabBar', () => {
|
|
6
|
+
it('shows Tapped Alerts snackbar when Alerts tab is clicked', () => {
|
|
7
|
+
cy.visit(url);
|
|
8
|
+
|
|
9
|
+
cy.contains('Alerts').click();
|
|
10
|
+
|
|
11
|
+
cy.contains('Tapped Alerts').should('be.visible');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('renders all-disabled tabs as unclickable', () => {
|
|
15
|
+
cy.visit(url);
|
|
16
|
+
|
|
17
|
+
cy.contains('All tabs disabled').should('be.visible');
|
|
18
|
+
cy.contains('One').should('be.visible');
|
|
19
|
+
cy.contains('Two').should('be.visible');
|
|
20
|
+
cy.contains('Three').should('be.visible');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
%PDF-1.4
|
|
2
|
+
1 0 obj<</Type /Catalog /Pages 2 0 R>>endobj 2 0 obj<</Type /Pages /Kids [3 0 R] /Count 1>>endobj 3 0 obj<</Type /Page /MediaBox [0 0 3 3]>>endobj
|
|
3
|
+
xref
|
|
4
|
+
0 4
|
|
5
|
+
0000000000 65535 f
|
|
6
|
+
0000000009 00000 n
|
|
7
|
+
0000000058 00000 n
|
|
8
|
+
0000000115 00000 n
|
|
9
|
+
trailer<</Size 4 /Root 1 0 R>>
|
|
10
|
+
startxref
|
|
11
|
+
190
|
|
12
|
+
%%EOF
|
|
Binary file
|
|
Binary file
|
package/cypress/helper.js
CHANGED
|
@@ -16,4 +16,16 @@ function withComponent(id, callback) {
|
|
|
16
16
|
})
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
function withComponentThen(id, callback) {
|
|
20
|
+
return cy.window()
|
|
21
|
+
.should((win) => {
|
|
22
|
+
const comp = realComponent(win.GLib.component.findById(id))
|
|
23
|
+
expect(comp).to.exist
|
|
24
|
+
expect(comp.$el instanceof win.HTMLElement).to.be.true
|
|
25
|
+
})
|
|
26
|
+
.then((win) => {
|
|
27
|
+
callback(realComponent(win.GLib.component.findById(id)))
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { testPageUrl, withComponent, withComponentThen }
|
package/cypress/support/e2e.js
CHANGED
|
@@ -15,21 +15,28 @@
|
|
|
15
15
|
|
|
16
16
|
// Import commands.js using ES2015 syntax:
|
|
17
17
|
import "./commands.js";
|
|
18
|
-
import "@cypress/code-coverage/support
|
|
19
|
-
import { isFunction, isObject } from "../../utils/type.js";
|
|
18
|
+
import "@cypress/code-coverage/support";
|
|
19
|
+
// import { isFunction, isObject } from "../../utils/type.js";
|
|
20
20
|
|
|
21
|
-
Cypress.on("window:before:load", (win) => {
|
|
22
|
-
|
|
21
|
+
// Cypress.on("window:before:load", (win) => {
|
|
22
|
+
// if (!isObject(win) || !isObject(win.console)) return;
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
// if (isFunction(win.console.log)) {
|
|
25
|
+
// win.console.log = () => {};
|
|
26
|
+
// }
|
|
27
|
+
// if (isFunction(win.console.warn)) {
|
|
28
|
+
// win.console.warn = () => {};
|
|
29
|
+
// }
|
|
30
|
+
// if (isFunction(win.console.info)) {
|
|
31
|
+
// win.console.info = () => {};
|
|
32
|
+
// }
|
|
33
|
+
// });
|
|
34
|
+
|
|
35
|
+
// Stripe and Google Maps telemetry requests block cy.visit() in Cypress 15+ due
|
|
36
|
+
// to stricter network idle detection. Stub them globally so they don't stall tests.
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
cy.intercept('https://m.stripe.com/**', { statusCode: 200, body: {}, log: false },);
|
|
39
|
+
cy.intercept('https://maps.googleapis.com/**', { statusCode: 200, body: {}, log: false });
|
|
33
40
|
});
|
|
34
41
|
|
|
35
42
|
// Ignore retry failures from backend error pages so specs can assert UI state.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
## Handling Dependabot Dependency Updates
|
|
2
|
+
|
|
3
|
+
### Review the changelog first
|
|
4
|
+
Before merging, check what changed between versions — especially for major bumps. Look for breaking changes, deprecated APIs, or behavior changes.
|
|
5
|
+
|
|
6
|
+
### Understand semver signals
|
|
7
|
+
- **Patch (1.0.x)** — bug/security fixes, generally safe to merge
|
|
8
|
+
- **Minor (1.x.0)** — new features, backward-compatible, low risk
|
|
9
|
+
- **Major (x.0.0)** — breaking changes likely, requires careful review and testing
|
|
10
|
+
|
|
11
|
+
The general rule: patch = merge quickly, minor = review and merge, major = treat like a migration task.
|
|
12
|
+
|
|
13
|
+
### Don't skip tests
|
|
14
|
+
Make sure CI runs the full test suite on each Dependabot PR before merging. If tests are absent, do a manual smoke test of critical paths.
|
|
15
|
+
|
|
16
|
+
For **minor version updates**, run the Cypress test suite locally before merging (`yarn test`). New features in a minor bump can introduce subtle behavior changes that unit tests won't catch.
|
|
17
|
+
|
|
18
|
+
### Check for dependency compatibility
|
|
19
|
+
Bumping one package can cause peer dependency conflicts with others. Review the `yarn.lock` diff — a surprisingly large change warrants a closer look. Also check that the updated package is compatible with other key dependencies (e.g. a Vite plugin bumped to support Vite 6 may break if you're still on Vite 5).
|
|
20
|
+
|
|
21
|
+
### Don't blindly auto-merge everything
|
|
22
|
+
Auto-merge is reasonable for patch-level updates in well-tested projects, but be cautious with minor/major bumps or packages central to the app (e.g. web framework, auth libraries).
|