glib-web 5.0.7 → 6.0.0
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.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/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
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { testPageUrl } from "../../helper.js";
|
|
2
|
+
|
|
3
|
+
const url = testPageUrl('fields_text');
|
|
4
|
+
|
|
5
|
+
describe('fields_text', () => {
|
|
6
|
+
it('shows status changed when Name field is edited', () => {
|
|
7
|
+
cy.visit(url);
|
|
8
|
+
|
|
9
|
+
cy.contains('Status: idle').should('exist');
|
|
10
|
+
cy.get('input[placeholder="Full name"]').type('John Doe');
|
|
11
|
+
cy.contains('Status: changed').should('exist');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('Set value fills the Name field with Hello world', () => {
|
|
15
|
+
cy.visit(url);
|
|
16
|
+
|
|
17
|
+
cy.contains('Set value').click();
|
|
18
|
+
cy.get('input[placeholder="Full name"]').should('have.value', 'Hello world');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('Clear value empties the Name field', () => {
|
|
22
|
+
cy.visit(url);
|
|
23
|
+
|
|
24
|
+
cy.contains('Set value').click();
|
|
25
|
+
cy.get('input[placeholder="Full name"]').should('have.value', 'Hello world');
|
|
26
|
+
cy.contains('Clear value').click();
|
|
27
|
+
cy.get('input[placeholder="Full name"]').should('have.value', '');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('Focus button focuses the Name field', () => {
|
|
31
|
+
cy.visit(url);
|
|
32
|
+
|
|
33
|
+
cy.contains('Focus').click();
|
|
34
|
+
cy.get('input[placeholder="Full name"]').should('be.focused');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('Reset button resets fields and shows status reset', () => {
|
|
38
|
+
cy.visit(url);
|
|
39
|
+
|
|
40
|
+
cy.get('input[placeholder="Full name"]').type('Changed text');
|
|
41
|
+
cy.contains('Reset').click();
|
|
42
|
+
cy.get('#text_status').scrollIntoView().should('contain', 'Status: reset');
|
|
43
|
+
cy.get('input[placeholder="Full name"]').should('have.value', '');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('password visibility toggle reveals and hides password text', () => {
|
|
47
|
+
cy.visit(url);
|
|
48
|
+
|
|
49
|
+
cy.get('input[placeholder="Enter your password"]').type('secret123');
|
|
50
|
+
cy.get('input[placeholder="Enter your password"]').should('have.attr', 'type', 'password');
|
|
51
|
+
cy.get('input[placeholder="Enter your password"]')
|
|
52
|
+
.closest('.v-input')
|
|
53
|
+
.find('i.material-icons[role="button"]')
|
|
54
|
+
.click();
|
|
55
|
+
cy.get('input[placeholder="Enter your password"]').should('have.attr', 'type', 'text');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('derived value updates when Base value changes', () => {
|
|
59
|
+
cy.visit(url);
|
|
60
|
+
|
|
61
|
+
cy.contains('Base value').closest('.v-input').find('input').clear().type('30');
|
|
62
|
+
cy.get('input[name="user[derived_value]"]').should('have.value', '10');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('password strength hint appears when password reaches 8 characters', () => {
|
|
66
|
+
cy.visit(url);
|
|
67
|
+
|
|
68
|
+
cy.get('input[placeholder="Type a password..."]').type('short');
|
|
69
|
+
cy.contains('Password length looks good').should('not.be.visible');
|
|
70
|
+
cy.get('input[placeholder="Type a password..."]').type('longpass');
|
|
71
|
+
cy.contains('Password length looks good').should('be.visible');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('Submit opens a dialog showing POST form data', () => {
|
|
75
|
+
cy.visit(url);
|
|
76
|
+
|
|
77
|
+
cy.contains('Submit').click();
|
|
78
|
+
cy.contains('Method: POST').should('be.visible');
|
|
79
|
+
cy.contains('Form Data:').should('be.visible');
|
|
80
|
+
cy.contains('OK').click();
|
|
81
|
+
cy.contains('Method: POST').should('not.exist');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -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 }
|