@windward/core 0.14.0 → 0.16.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/bitbucket-pipelines.yml +8 -0
  3. package/components/Content/Blocks/BlockQuote.vue +51 -47
  4. package/components/Content/Blocks/GenerateAIQuestionButton.vue +151 -37
  5. package/components/Content/Blocks/OpenResponse.vue +11 -3
  6. package/components/Content/Blocks/UserUpload/ManageDataTableUserFiles.vue +2 -11
  7. package/components/Content/Blocks/Video.vue +1 -1
  8. package/components/Glossary/GlossaryVerification.vue +240 -0
  9. package/components/Navigation/Items/GlossaryNav.vue +8 -2
  10. package/components/Navigation/Items/UserUploadNav.vue +32 -13
  11. package/components/Settings/AccordionSettings.vue +37 -27
  12. package/components/Settings/ClickableIconsSettings.vue +1 -0
  13. package/components/Settings/ImageSettings.vue +5 -12
  14. package/components/Settings/TabSettings.vue +33 -23
  15. package/components/Settings/TextEditorSettings.vue +16 -245
  16. package/components/Settings/VideoSettings.vue +1 -1
  17. package/components/utils/ContentViewer.vue +3 -3
  18. package/components/utils/TinyMCEWrapper.vue +24 -14
  19. package/components/utils/glossary/CourseGlossary.vue +75 -95
  20. package/components/utils/glossary/CourseGlossaryForm.vue +42 -13
  21. package/components/utils/glossary/GlossaryToolTip.vue +7 -11
  22. package/helpers/GlossaryHelper.ts +18 -16
  23. package/i18n/en-US/components/content/blocks/generate_questions.ts +26 -11
  24. package/i18n/en-US/pages/glossary.ts +3 -1
  25. package/i18n/en-US/shared/content_blocks.ts +1 -1
  26. package/i18n/en-US/shared/notification.ts +5 -0
  27. package/i18n/en-US/shared/permission.ts +4 -0
  28. package/i18n/en-US/shared/settings.ts +1 -1
  29. package/i18n/es-ES/components/content/blocks/generate_questions.ts +11 -1
  30. package/i18n/es-ES/pages/glossary.ts +3 -1
  31. package/i18n/es-ES/shared/content_blocks.ts +1 -1
  32. package/i18n/es-ES/shared/notification.ts +5 -0
  33. package/i18n/es-ES/shared/permission.ts +8 -4
  34. package/i18n/es-ES/shared/settings.ts +1 -2
  35. package/i18n/sv-SE/components/content/blocks/generate_questions.ts +11 -1
  36. package/i18n/sv-SE/pages/glossary.ts +3 -1
  37. package/i18n/sv-SE/shared/content_blocks.ts +1 -1
  38. package/i18n/sv-SE/shared/notification.ts +4 -0
  39. package/i18n/sv-SE/shared/permission.ts +8 -4
  40. package/i18n/sv-SE/shared/settings.ts +1 -1
  41. package/jest.config.js +3 -0
  42. package/models/BaseModel.ts +16 -0
  43. package/models/CourseGlossaryTerm.ts +22 -0
  44. package/models/UserFileAsset.ts +1 -0
  45. package/package.json +4 -3
  46. package/pages/glossary.vue +41 -4
  47. package/pages/userUpload.vue +70 -47
  48. package/plugin.js +34 -12
  49. package/store/glossary.js +57 -0
  50. package/test/Components/Content/Blocks/Feedback.spec.js +3 -1
  51. package/test/Components/Content/Blocks/Video.spec.js +2 -2
  52. package/test/Components/Glossary/GlossaryVerification.spec.js +19 -0
  53. package/test/Components/utils/glossary/CourseGlossary.spec.js +19 -0
  54. package/test/Components/utils/glossary/CourseGlossaryForm.spec.js +28 -0
  55. package/test/__mocks__/helpersMock.js +0 -24
  56. package/test/__mocks__/modelMock.js +5 -0
  57. package/test/helpers/GlossaryHelper.spec.js +32 -14
  58. package/test/mocks.js +11 -0
  59. package/test/setup/before.js +15 -0
  60. package/helpers/GlossaryTerm.ts +0 -31
@@ -0,0 +1,57 @@
1
+ import _ from 'lodash'
2
+ import Course from '~/models/Course'
3
+ import CourseGlossaryTerm from '../models/CourseGlossaryTerm'
4
+
5
+ export default {
6
+ // Required so these states / methods are placed under glossary/get and not just get
7
+ namespaced: true,
8
+ state: () => ({
9
+ glossary: {
10
+ course: null,
11
+ terms: [],
12
+ },
13
+ }),
14
+
15
+ getters: {
16
+ get(state) {
17
+ return state.glossary
18
+ },
19
+ getCourse(state) {
20
+ return state.glossary.course
21
+ },
22
+ getTerms(state) {
23
+ return state.glossary.terms
24
+ },
25
+ },
26
+
27
+ mutations: {
28
+ setTerms(state, terms) {
29
+ state.glossary.terms = _.cloneDeep(terms)
30
+ },
31
+ setCourse(state, course) {
32
+ state.glossary.course = _.cloneDeep(course)
33
+ },
34
+ },
35
+
36
+ actions: {
37
+ clear(context) {
38
+ context.commit('setTerms', [])
39
+ context.commit('setCourse', null)
40
+ },
41
+ async load(context, course) {
42
+ try {
43
+ // Store the course that these terms are in context to
44
+ context.commit('setCourse', course)
45
+
46
+ const terms = await CourseGlossaryTerm.custom(
47
+ new Course(course),
48
+ new CourseGlossaryTerm()
49
+ ).get()
50
+
51
+ context.commit('setTerms', terms)
52
+ } catch (e) {
53
+ console.error('Error loading glossary terms:', e)
54
+ }
55
+ },
56
+ },
57
+ }
@@ -18,7 +18,9 @@ describe('Feedback', () => {
18
18
  config: {
19
19
  definition: {
20
20
  metadata: {
21
- definition: { body: 'test', response: '' },
21
+ definition: [
22
+ { body: 'test', response: '' },
23
+ ],
22
24
  },
23
25
  },
24
26
  },
@@ -1,7 +1,6 @@
1
1
  import { mount } from '@vue/test-utils'
2
- import Vuetify from 'vuetify'
3
2
  import Vue from 'vue'
4
-
3
+ import Vuetify from 'vuetify'
5
4
  import { defaultMocks } from '@/test/mocks'
6
5
 
7
6
  jest.mock('@mindedge/vuetify-player', () => {
@@ -47,6 +46,7 @@ const metadata = {
47
46
  type: 'video/mp4',
48
47
  },
49
48
  ],
49
+ tracks: [],
50
50
  },
51
51
  ],
52
52
  },
@@ -0,0 +1,19 @@
1
+ import { shallowMount } from '@vue/test-utils'
2
+ import Vue from 'vue'
3
+ import Vuetify from 'vuetify'
4
+ import { defaultMocks } from '@/test/mocks'
5
+ import GlossaryVerification from '@/components/Glossary/GlossaryVerification.vue'
6
+
7
+ Vue.use(Vuetify)
8
+
9
+ describe('GlossaryVerification component', () => {
10
+ test('is a Vue instance', () => {
11
+ const wrapper = shallowMount(GlossaryVerification, {
12
+ vuetify: new Vuetify(),
13
+ mocks: defaultMocks,
14
+ propsData: {},
15
+ })
16
+
17
+ expect(wrapper.vm).toBeTruthy()
18
+ })
19
+ })
@@ -0,0 +1,19 @@
1
+ import { shallowMount } from '@vue/test-utils'
2
+ import Vue from 'vue'
3
+ import Vuetify from 'vuetify'
4
+ import { defaultMocks } from '@/test/mocks'
5
+ import CourseGlossary from '@/components/utils/glossary/CourseGlossary.vue'
6
+
7
+ Vue.use(Vuetify)
8
+
9
+ describe('CourseGlossary component', () => {
10
+ test('is a Vue instance', () => {
11
+ const wrapper = shallowMount(CourseGlossary, {
12
+ vuetify: new Vuetify(),
13
+ mocks: defaultMocks,
14
+ propsData: {},
15
+ })
16
+
17
+ expect(wrapper.vm).toBeTruthy()
18
+ })
19
+ })
@@ -0,0 +1,28 @@
1
+ import { shallowMount } from '@vue/test-utils'
2
+ import Vue from 'vue'
3
+ import Vuetify from 'vuetify'
4
+ import { defaultMocks } from '@/test/mocks'
5
+ import CourseGlossaryForm from '@/components/utils/glossary/CourseGlossaryForm.vue'
6
+
7
+ Vue.use(Vuetify)
8
+
9
+ describe('CourseGlossaryForm component', () => {
10
+ test('is a Vue instance', () => {
11
+ const wrapper = shallowMount(CourseGlossaryForm, {
12
+ vuetify: new Vuetify(),
13
+ mocks: defaultMocks,
14
+ propsData: {
15
+ value: {
16
+ term: '',
17
+ definition: '',
18
+ alternate_forms: [],
19
+ related_terms: [],
20
+ },
21
+ glossary: [],
22
+ editMode: false,
23
+ },
24
+ })
25
+
26
+ expect(wrapper.vm).toBeTruthy()
27
+ })
28
+ })
@@ -39,27 +39,3 @@ jest.mock(
39
39
  },
40
40
  { virtual: true }
41
41
  )
42
-
43
- jest.mock(
44
- 'lodash',
45
- () => {
46
- return {
47
- cloneDeep: (v) => {
48
- return JSON.parse(JSON.stringify(v))
49
- },
50
- isEmpty: () => {
51
- return jest.fn()
52
- },
53
- isBoolean: () => {
54
- return jest.fn()
55
- },
56
- flatten: () => {
57
- return jest.fn()
58
- },
59
- get: () => {
60
- return jest.fn()
61
- },
62
- }
63
- },
64
- { virtual: true }
65
- )
@@ -55,7 +55,12 @@ const mockVirtualModels = [
55
55
  ]
56
56
 
57
57
  const mockModels = [
58
+ { path: '../../models/BaseModel', resource: '' },
58
59
  { path: '../../models/UserFileAsset', resource: 'user-files' },
60
+ {
61
+ path: '../../models/CourseGlossaryTerm',
62
+ resource: 'course-glossary-terms',
63
+ },
59
64
  ]
60
65
 
61
66
  // DO NOT ALTER THE BELOW CODE
@@ -1,5 +1,6 @@
1
+ import { defaultMocks } from '@/test/mocks'
1
2
  import GlossaryHelper from '../../helpers/GlossaryHelper'
2
- import GlossaryTerm from '../../helpers/GlossaryTerm'
3
+ import CourseGlossaryTerm from '../../models/CourseGlossaryTerm'
3
4
 
4
5
  const contentHtml =
5
6
  '<p>Voluptas est quis ut <strong>consequatur.</strong>' +
@@ -32,14 +33,16 @@ const glossary = [
32
33
  {
33
34
  term: 'veniam',
34
35
  definition: 'venom is venomus',
35
- alternate_forms: null,
36
- related_term: null,
36
+ alternate_forms: [],
37
+ related_terms: [],
37
38
  },
38
39
  {
39
40
  term: 'Architecto',
40
41
  definition: 'Architecto is a word that means architect of the universe',
41
42
  alternate_forms: ['Architect', 'universe'],
42
- related_term: 'a,b,c,d',
43
+ related_terms: [
44
+ { id: '00000000-0000-0000-1000-000000000000', term: 'a,b,c,d' },
45
+ ],
43
46
  },
44
47
  {
45
48
  term: 'Voluptatem',
@@ -47,14 +50,14 @@ const glossary = [
47
50
  'Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of ' +
48
51
  'classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock,' +
49
52
  ' a Latin professor at Hampden-Sydney College in Virginia',
50
- alternate_forms: null,
51
- related_term: null,
53
+ alternate_forms: [],
54
+ related_terms: [],
52
55
  },
53
56
  {
54
57
  term: 'crisis change',
55
58
  definition: 'n/a',
56
59
  alternate_forms: ['crisis', 'change'],
57
- related_term: null,
60
+ related_terms: [],
58
61
  },
59
62
  {
60
63
  term: 'valores periféricos',
@@ -62,6 +65,7 @@ const glossary = [
62
65
  'Valores que son importantes pero no esenciales para una organización.',
63
66
  tags: 'valores periféricos',
64
67
  alternate_forms: [],
68
+ related_terms: [],
65
69
  },
66
70
  ]
67
71
  const glossaryRender =
@@ -72,12 +76,14 @@ const glossaryRender =
72
76
  '<plugin-core-glossary-tool-tip>' +
73
77
  '<template v-slot:term>Architecto</template>' +
74
78
  '<template v-slot:definition>Architecto is a word that means architect of the universe</template>' +
79
+ '<template v-slot:alternate_forms>Architect,universe</template>' +
75
80
  '<template v-slot:related_terms>a,b,c,d</template>' +
76
81
  '</plugin-core-glossary-tool-tip>'
77
82
  const glossaryRenderMultiple =
78
83
  '<plugin-core-glossary-tool-tip>' +
79
84
  '<template v-slot:term>Architecto</template>' +
80
85
  '<template v-slot:definition>Architecto is a word that means architect of the universe</template>' +
86
+ '<template v-slot:alternate_forms>Architect,universe</template>' +
81
87
  '<template v-slot:related_terms>a,b,c,d</template>' +
82
88
  '</plugin-core-glossary-tool-tip>' +
83
89
  '<p>Voluptas est quis ut <strong>consequatur.</strong> ' +
@@ -87,14 +93,16 @@ const glossaryRenderMultiple =
87
93
  '<plugin-core-glossary-tool-tip>' +
88
94
  '<template v-slot:term>Architecto</template>' +
89
95
  '<template v-slot:definition>Architecto is a word that means architect of the universe</template>' +
96
+ '<template v-slot:alternate_forms>Architect,universe</template>' +
90
97
  '<template v-slot:related_terms>a,b,c,d</template>' +
91
98
  '</plugin-core-glossary-tool-tip>'
92
99
 
93
- const term = new GlossaryTerm(glossary[1])
100
+ const term = new CourseGlossaryTerm(glossary[1])
94
101
  const expectedToolTip =
95
102
  '<plugin-core-glossary-tool-tip>' +
96
103
  '<template v-slot:term>Architecto</template>' +
97
104
  '<template v-slot:definition>Architecto is a word that means architect of the universe</template>' +
105
+ '<template v-slot:alternate_forms>Architect,universe</template>' +
98
106
  '<template v-slot:related_terms>a,b,c,d</template>' +
99
107
  '</plugin-core-glossary-tool-tip>'
100
108
  const glossaryRenderCapitalized =
@@ -105,6 +113,7 @@ const glossaryRenderCapitalized =
105
113
  '<plugin-core-glossary-tool-tip>' +
106
114
  '<template v-slot:term>Crisis change</template>' +
107
115
  '<template v-slot:definition>n/a</template>' +
116
+ '<template v-slot:alternate_forms>crisis,change</template>' +
108
117
  '</plugin-core-glossary-tool-tip>'
109
118
  const glossaryRenderCapitalizedSpaceBefore =
110
119
  '<p>Voluptas est quis ut <strong>consequatur.</strong> ' +
@@ -114,6 +123,7 @@ const glossaryRenderCapitalizedSpaceBefore =
114
123
  '<plugin-core-glossary-tool-tip>' +
115
124
  '<template v-slot:term> Crisis change</template>' +
116
125
  '<template v-slot:definition>n/a</template>' +
126
+ '<template v-slot:alternate_forms>crisis,change</template>' +
117
127
  '</plugin-core-glossary-tool-tip>'
118
128
  const glossaryRenderCapitalizedSpaceAfter =
119
129
  '<p>Voluptas est quis ut <strong>consequatur.</strong> ' +
@@ -123,6 +133,7 @@ const glossaryRenderCapitalizedSpaceAfter =
123
133
  '<plugin-core-glossary-tool-tip>' +
124
134
  '<template v-slot:term>Crisis change </template>' +
125
135
  '<template v-slot:definition>n/a</template>' +
136
+ '<template v-slot:alternate_forms>crisis,change</template>' +
126
137
  '</plugin-core-glossary-tool-tip>'
127
138
  const glossaryRenderCapitalizedSpaceBeforeAfter =
128
139
  '<p>Voluptas est quis ut <strong>consequatur.</strong> ' +
@@ -132,6 +143,7 @@ const glossaryRenderCapitalizedSpaceBeforeAfter =
132
143
  '<plugin-core-glossary-tool-tip>' +
133
144
  '<template v-slot:term> Crisis change </template>' +
134
145
  '<template v-slot:definition>n/a</template>' +
146
+ '<template v-slot:alternate_forms>crisis,change</template>' +
135
147
  '</plugin-core-glossary-tool-tip>'
136
148
  describe('GlossaryHelper ', () => {
137
149
  test('detects text has glossary words', () => {
@@ -162,7 +174,7 @@ describe('GlossaryHelper ', () => {
162
174
  accentedGlossaryTerm + contentHtml,
163
175
  glossary
164
176
  )
165
- ).toEqual([new GlossaryTerm(glossary[4])])
177
+ ).toEqual([new CourseGlossaryTerm(glossary[4])])
166
178
  })
167
179
  test('can retrieve un-verified glossary terms', () => {
168
180
  expect(
@@ -170,7 +182,9 @@ describe('GlossaryHelper ', () => {
170
182
  glossaryWord2 + contentHtml + glossaryWord2,
171
183
  glossary
172
184
  )
173
- ).toEqual([new GlossaryTerm({ term: 'unverified', definition: 'n/a' })])
185
+ ).toEqual([
186
+ new CourseGlossaryTerm({ term: 'unverified', definition: 'n/a' }),
187
+ ])
174
188
  })
175
189
  test('does not mistake alternate forms for un-verified glossary terms', () => {
176
190
  expect(
@@ -178,7 +192,9 @@ describe('GlossaryHelper ', () => {
178
192
  glossaryWord2 + contentHtml + glossaryWord4,
179
193
  glossary
180
194
  )
181
- ).toEqual([new GlossaryTerm({ term: 'unverified', definition: 'n/a' })])
195
+ ).toEqual([
196
+ new CourseGlossaryTerm({ term: 'unverified', definition: 'n/a' }),
197
+ ])
182
198
  })
183
199
 
184
200
  test('can retrieve un-verified glossary terms with space', () => {
@@ -188,8 +204,8 @@ describe('GlossaryHelper ', () => {
188
204
  glossary
189
205
  )
190
206
  ).toEqual([
191
- new GlossaryTerm({ term: 'Climate', definition: 'n/a' }),
192
- new GlossaryTerm({ term: 'unverified', definition: 'n/a' }),
207
+ new CourseGlossaryTerm({ term: 'Climate', definition: 'n/a' }),
208
+ new CourseGlossaryTerm({ term: 'unverified', definition: 'n/a' }),
193
209
  ])
194
210
  })
195
211
 
@@ -199,7 +215,9 @@ describe('GlossaryHelper ', () => {
199
215
  glossaryWord2 + contentHtml + glossaryWord,
200
216
  glossary
201
217
  )
202
- ).toEqual([new GlossaryTerm({ term: 'unverified', definition: 'n/a' })])
218
+ ).toEqual([
219
+ new CourseGlossaryTerm({ term: 'unverified', definition: 'n/a' }),
220
+ ])
203
221
  })
204
222
  test('can render glossary tooltip component', () => {
205
223
  expect(GlossaryHelper.makeToolTip(term, 'Architecto')).toEqual(
package/test/mocks.js CHANGED
@@ -46,6 +46,17 @@ jest.mock(
46
46
  // Just return an empty object
47
47
  return getters
48
48
  },
49
+ mapActions(obj) {
50
+ const actions = {}
51
+ for (const [key, value] of Object.entries(obj)) {
52
+ // Return a function since mapActions only work in the methods section
53
+ actions[key] = () => {
54
+ return
55
+ }
56
+ }
57
+ // Just return an empty object
58
+ return actions
59
+ },
49
60
  }
50
61
  },
51
62
  { virtual: true }
@@ -0,0 +1,15 @@
1
+ // Mocks to run BEFORE any other imports take place
2
+ // window.matchMedia is required for tinyMCE unit tests since it doesn't check if that function exists
3
+ Object.defineProperty(window, 'matchMedia', {
4
+ writable: true,
5
+ value: jest.fn().mockImplementation((query) => ({
6
+ matches: false,
7
+ media: query,
8
+ onchange: null,
9
+ addListener: jest.fn(), // Deprecated
10
+ removeListener: jest.fn(), // Deprecated
11
+ addEventListener: jest.fn(),
12
+ removeEventListener: jest.fn(),
13
+ dispatchEvent: jest.fn(),
14
+ })),
15
+ })
@@ -1,31 +0,0 @@
1
- class GlossaryTerm {
2
-
3
- term!: string
4
-
5
- definition!: string
6
-
7
- alternate_forms: any = null
8
-
9
- related_term: any =null
10
-
11
- private required: string[] = ['term', 'definition']
12
-
13
- constructor(params: any) {
14
- this.required.forEach((field) => {
15
- if (!params[field]) {
16
- throw new Error('Glossary term ' + field + ' is required')
17
- }
18
- })
19
-
20
- this.term = params.term
21
- this.definition = params.definition
22
- this.alternate_forms = params.alternate_forms || null
23
- this.related_term = params.related_term || null
24
- }
25
-
26
- public static toString(){
27
-
28
- }
29
- }
30
-
31
- export default GlossaryTerm