diff --git a/client/cypress/integration/frontend/rooms/article-page.spec.js b/client/cypress/integration/frontend/rooms/article-page.spec.js index 3830ed0a..692b9101 100644 --- a/client/cypress/integration/frontend/rooms/article-page.spec.js +++ b/client/cypress/integration/frontend/rooms/article-page.spec.js @@ -3,11 +3,22 @@ import {getMinimalMe} from '../../../support/helpers'; describe('Article page', () => { const slug = 'this-article-has-a-slug'; const roomEntry = { - slug, - id: 'room-entry-id', - title: 'Some Room Entry, yay!', - comments: [], - }; + slug, + id: 'room-entry-id', + title: 'Some Room Entry, yay!', + comments: [], + contents: [{ + type: 'text_block', + value: { + text: 'Ein Text', + }, + }, { + type: 'subtitle', + value: { + text: 'Ein Untertitel' + } + }], + }; const operations = { MeQuery: getMinimalMe({}), @@ -23,18 +34,28 @@ describe('Article page', () => { roomEntry: roomEntry, owner: { firstName: 'Matt', - lastName: 'Damon' - } - } - } + lastName: 'Damon', + }, + }, + }, }; - } + }, }; beforeEach(() => { cy.setup(); }); + it('shows the article with contents', () => { + cy.mockGraphqlOps({ + operations, + }); + + cy.visit(`/article/${slug}`); + cy.getByDataCy('text-block').should('contain.text', 'Ein Text'); + cy.getByDataCy('subtitle-block').should('contain.text', 'Ein Untertitel'); + }); + it('goes to article and leaves a comment', () => { cy.mockGraphqlOps({ operations, diff --git a/client/cypress/integration/frontend/rooms/room-page.spec.js b/client/cypress/integration/frontend/rooms/room-page.spec.js index d6f5f8c2..83111a36 100644 --- a/client/cypress/integration/frontend/rooms/room-page.spec.js +++ b/client/cypress/integration/frontend/rooms/room-page.spec.js @@ -1,6 +1,6 @@ import {getMinimalMe} from '../../../support/helpers'; -describe('The Room Page', () => { +describe('The Room Page (Teacher)', () => { const MeQuery = getMinimalMe(); const selectedClass = MeQuery.me.selectedClass; const entryText = 'something should be here'; @@ -61,32 +61,17 @@ describe('The Room Page', () => { }); cy.visit(`/room/${slug}`); - cy.get('[data-cy=add-room-entry-button]').click(); - cy.get('.add-content-element:first-of-type').click(); - cy.get('[data-cy=choose-text-widget]').click(); - cy.get('[data-cy=modal-title-input] > .modal-input__inputfield').type(entryTitle); + cy.getByDataCy('add-room-entry-button').click(); + cy.getByDataCy('add-content-link').first().click(); + cy.getByDataCy('choose-text-widget').click(); + cy.getByDataCy('input-with-label-input').type(entryTitle); - cy.get('[data-cy=text-form-input]').type(entryText); - cy.get('[data-cy=modal-save-button]').click(); + cy.get('.tip-tap__editor').type(entryText); + cy.getByDataCy('save-button').click(); cy.get('.room-entry__content:first').should('contain', entryText).should('contain', 'Rachel Green'); }); - it('room actions should not exist for student', () => { - const operations = { - MeQuery: getMinimalMe({isTeacher: false}), - RoomEntriesQuery, - }; - - cy.mockGraphqlOps({ - operations, - }); - cy.visit(`/room/${slug}`); - - cy.getByDataCy('room-title').should('exist'); - cy.getByDataCy('room-actions').should('not.exist'); - }); - // todo: re-enable once cypress can do it correctly it.skip('changes visibility of a room', () => { const MeQuery = getMinimalMe({ @@ -149,6 +134,7 @@ describe('The Room Page', () => { }; const otherRoom = { id: btoa('RoomNode:otherRoom'), + slug: 'other-slug', schoolClass, }; let rooms = [roomToDelete, otherRoom]; @@ -156,7 +142,7 @@ describe('The Room Page', () => { MeQuery, RoomsQuery() { return { - rooms + rooms, }; }, RoomEntriesQuery: { @@ -176,47 +162,126 @@ describe('The Room Page', () => { cy.getByDataCy('room-widget').should('have.length', 2); cy.getByDataCy('room-widget').first().click(); cy.getByDataCy('toggle-more-actions-menu').click(); - cy.getByDataCy('delete-room').click(); + cy.getByDataCy('delete-room').within(() => { + cy.get('a').click(); + }); cy.url().should('include', 'rooms'); cy.getByDataCy('room-widget').should('have.length', 1); }); - it('edits own room entry', () => { - const MeQuery = getMinimalMe({isTeacher: false}); + it('changes class while on room page', () => { const {me} = MeQuery; - const id = atob(me.id).split(':')[1]; - const authorId = btoa(`PublicUserNode:${id}`); - const room = { - id: 'some-room', - roomEntries: { - edges: [ - { - node: { - id: '', - slug: '', - contents: [], - author: { - ...me, - id: authorId, - }, - }, - }, - ], - }, + const otherClass = { + id: btoa('SchoolClassNode:34'), + name: 'Other Class', + readOnly: false, }; + let selectedClass = me.selectedClass; const operations = { - MeQuery: MeQuery, - RoomEntriesQuery: { - room, + MeQuery: () => { + return { + me: { + ...me, + schoolClasses: [...me.schoolClasses, otherClass], + selectedClass, + }, + }; + }, + RoomEntriesQuery, + UpdateSettings() { + selectedClass = otherClass; + return { + updateSettings: { + success: true, + }, + }; + }, + ModuleDetailsQuery: {}, + MySchoolClassQuery: () => { + return { + me: { + selectedClass, + }, + }; + }, + RoomsQuery: { + rooms: [], }, - RoomEntryQuery: {}, }; + cy.mockGraphqlOps({ operations, }); cy.visit(`/room/${slug}`); - cy.getByDataCy('room-entry-actions').click(); - cy.getByDataCy('edit-room-entry').click(); + cy.getByDataCy('room-title').should('contain', 'A Room'); + cy.selectClass('Other Class'); + cy.url().should('include', 'rooms'); + cy.getByDataCy('current-class-name').should('contain', 'Other Class'); + }); +}); + +describe('The Room Page (student)', () => { + const slug = 'ein-historisches-festival'; + const MeQuery = getMinimalMe({isTeacher: false}); + const {me} = MeQuery; + const id = atob(me.id).split(':')[1]; + const authorId = btoa(`PublicUserNode:${id}`); + const entrySlug = 'entry-slug'; + const {selectedClass} = me; + const roomEntry = { + id: 'entry-id', + slug: entrySlug, + title: 'My Entry', + contents: [ + { + type: 'text_block', + value: { + text: 'some text', + }, + }, + ], + comments: [{}, {}], + author: { + ...me, + id: authorId, + firstName: 'Hans', + lastName: 'Was Heiri', + avatarUrl: '', + }, + }; + const room = { + id, + slug, + schoolClass: selectedClass, + restricted: false, + roomEntries: { + edges: [{ + node: roomEntry, + }], + }, + }; + + const RoomEntriesQuery = { + room, + }; + + beforeEach(() => { + cy.setup(); + }); + + it('room actions should not exist for student', () => { + const operations = { + MeQuery: getMinimalMe({isTeacher: false}), + RoomEntriesQuery, + }; + + cy.mockGraphqlOps({ + operations, + }); + cy.visit(`/room/${slug}`); + + cy.getByDataCy('room-title').should('exist'); + cy.getByDataCy('room-actions').should('not.exist'); }); it('creates a room entry', () => { @@ -240,56 +305,83 @@ describe('The Room Page', () => { cy.visit(`/room/${slug}`); cy.getByDataCy('add-room-entry-button').click(); - cy.getByDataCy('add-room-entry-modal').should('exist'); + + cy.getByDataCy('content-form-section-title').should('have.text', 'Titel (Pflichtfeld)'); }); - it.only('changes class while on room page', () => { - const {me} = MeQuery; - const otherClass = { - id: btoa('SchoolClassNode:34'), - name: 'Other Class', - readOnly: false + it('edits own room entry', () => { + const room = { + id: 'some-room', + slug, + // schoolClass: me.selectedClass, + roomEntries: { + edges: [ + { + node: roomEntry, + }, + ], + }, }; - let selectedClass = me.selectedClass; const operations = { - MeQuery: () => { - return { - me: { - ...me, - schoolClasses: [...me.schoolClasses, otherClass], - selectedClass - }, - }; + MeQuery: MeQuery, + RoomEntriesQuery: { + room, }, + RoomEntryQuery: { + roomEntry, + }, + }; + cy.mockGraphqlOps({ + operations, + }); + cy.visit(`/room/${slug}`); + cy.getByDataCy('room-entry-actions').click(); + cy.getByDataCy('edit-room-entry').click(); + cy.location('pathname').should('include', entrySlug); + }); + + it('deletes room entry', () => { + const DeleteRoomEntry = { + deleteRoomEntry: { + success: true, + errors: null, + roomSlug: slug, + }, + }; + + const operations = { + MeQuery, RoomEntriesQuery, - UpdateSettings() { - selectedClass = otherClass; - return { - updateSettings: { - success: true - } - }; - }, - ModuleDetailsQuery: {}, - MySchoolClassQuery: () => { - return { - me: { - selectedClass - } - }; - }, - RoomsQuery: { - rooms: [] - } + DeleteRoomEntry, }; cy.mockGraphqlOps({ operations, }); + cy.visit(`/room/${slug}`); - cy.getByDataCy('room-title').should('contain', 'A Room'); - cy.selectClass('Other Class'); - cy.url().should('include', 'rooms'); - cy.getByDataCy('current-class-name').should('contain', 'Other Class'); + cy.getByDataCy('room-entry').should('have.length', 1); + cy.getByDataCy('room-entry-actions').click(); + cy.getByDataCy('delete-room-entry').click(); + cy.getByDataCy('delete-room-entry').should('not.exist'); + cy.getByDataCy('modal-save-button').click(); + cy.getByDataCy('room-entry').should('have.length', 0); + + }); + + it('shows room entries with comment count', () => { + const operations = { + MeQuery, + RoomEntriesQuery, + }; + cy.mockGraphqlOps({ + operations, + }); + + cy.visit(`/room/${slug}`); + cy.getByDataCy('room-entry').should('have.length', 1).within(() => { + cy.getByDataCy('entry-count').should('contain.text', '2'); + }); + }); }); diff --git a/client/src/App.vue b/client/src/App.vue index cd1dfe2b..0905d8c3 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -27,7 +27,6 @@ const NewContentBlockWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/content-block-form/NewContentBlockWizard'); const EditContentBlockWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/content-block-form/EditContentBlockWizard'); - const NewRoomEntryWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/rooms/room-entries/NewRoomEntryWizard'); const EditRoomEntryWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/rooms/room-entries/EditRoomEntryWizard'); const NewProjectEntryWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/portfolio/NewProjectEntryWizard'); const EditProjectEntryWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/portfolio/EditProjectEntryWizard'); @@ -58,7 +57,6 @@ SplitLayout, NewContentBlockWizard, EditContentBlockWizard, - NewRoomEntryWizard, EditRoomEntryWizard, NewProjectEntryWizard, EditProjectEntryWizard, diff --git a/client/src/components/content-block-form/ContentBlockForm.vue b/client/src/components/content-block-form/ContentBlockForm.vue index f8901aa2..dd933f50 100644 --- a/client/src/components/content-block-form/ContentBlockForm.vue +++ b/client/src/components/content-block-form/ContentBlockForm.vue @@ -14,11 +14,15 @@ :checked="localContentBlock.isAssignment" class="content-block-form__task-toggle" label="Inhaltsblock als Auftrag formatieren" + v-if="hasDefaultFeatures" @input="localContentBlock.isAssignment=$event" /> - + , required: true, }, + features: { + type: String, + default: DEFAULT_FEATURE_SET + } + }, + provide(): object { + return { + features: this.features + }; }, components: { ContentElementActions, @@ -161,7 +178,7 @@ ContentFormSection, Toggle, }, - data() { + data(): ContentBlockFormData { return { localContentBlock: Object.assign({}, { title: this.contentBlock.title, @@ -176,6 +193,9 @@ isValid(): boolean { return this.localContentBlock.title > ''; }, + hasDefaultFeatures(): boolean { + return this.features === DEFAULT_FEATURE_SET; + } }, methods: { update(index: number, element: any, parent?: number) { diff --git a/client/src/components/content-block-form/ContentFormSection.vue b/client/src/components/content-block-form/ContentFormSection.vue index 3539e56b..3941c1f7 100644 --- a/client/src/components/content-block-form/ContentFormSection.vue +++ b/client/src/components/content-block-form/ContentFormSection.vue @@ -4,7 +4,10 @@ {{ title }} + /> {{ title }}
diff --git a/client/src/components/content-blocks/TextBlock.vue b/client/src/components/content-blocks/TextBlock.vue index 8ce03d97..21caa05e 100644 --- a/client/src/components/content-blocks/TextBlock.vue +++ b/client/src/components/content-blocks/TextBlock.vue @@ -2,6 +2,7 @@
diff --git a/client/src/components/content-forms/ChooserElement.vue b/client/src/components/content-forms/ChooserElement.vue new file mode 100644 index 00000000..5a8672d9 --- /dev/null +++ b/client/src/components/content-forms/ChooserElement.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/client/src/components/content-forms/ContentBlockElementChooserWidget.vue b/client/src/components/content-forms/ContentBlockElementChooserWidget.vue index 3df6777b..e8bfdccd 100644 --- a/client/src/components/content-forms/ContentBlockElementChooserWidget.vue +++ b/client/src/components/content-forms/ContentBlockElementChooserWidget.vue @@ -13,7 +13,7 @@ Neuer Inhalt
@@ -107,8 +44,10 @@ import Checkbox from '@/components/ui/Checkbox'; import formElementIcons from '@/components/ui/form-element-icons'; - import TitleIcon from '@/components/icons/TitleIcon'; import CrossIcon from '@/components/icons/CrossIcon'; + import ChooserElement from '@/components/content-forms/ChooserElement'; + import {DEFAULT_FEATURE_SET} from '@/consts/features.consts'; + export default { props: { @@ -124,16 +63,72 @@ }, }, + inject: ['features'], + components: { + ChooserElement, CrossIcon, - TitleIcon, Checkbox, ...formElementIcons, }, - data: () => ({ - convertToList: false, - }), + data() { + const hasDefaultFeatures = this.features === DEFAULT_FEATURE_SET; + return { + convertToList: false, + chooserTypes: [ + { + type: 'subtitle', + block: 'subtitle', + title: 'Untertitel', + icon: 'title-icon', + }, + { + type: 'link', + block: 'link_block', + title: 'Link', + icon: 'link-icon', + }, + { + type: 'video', + block: 'video_block', + }, + { + type: 'image', + block: 'image_url_block', + title: 'Bild', + }, + { + type: 'text', + block: 'text_block', + }, + { + type: 'assignment', + block: 'assignment', + icon: 'speech-bubble-icon', + title: 'Aufgabe & Ergebnis', + show: !this.hideAssignment && hasDefaultFeatures + }, + { + type: 'document', + block: 'document_block', + title: 'Dokument', + show: hasDefaultFeatures + }, + + + ], + }; + }, + + computed: { + filteredChooserTypes() { + return this.chooserTypes.filter(type => !("show" in type) || type.show ); // display element if `show` is not set or if `show` evaluates to true + }, + hasDefaultFeatures() { + return this.features === DEFAULT_FEATURE_SET; + } + }, methods: { changeType(type) { @@ -144,7 +139,7 @@ }, remove() { this.$emit('remove'); - } + }, }, }; @@ -222,29 +217,6 @@ grid-row: 1; } - &__link { - cursor: pointer; - border: 1px solid $color-silver; - border-radius: 4px; - height: 105px; - width: 105px; - box-sizing: border-box; - display: grid; - grid-template-rows: 1fr 45px; - justify-content: center; - justify-items: center; - align-items: center; - } - &__link-icon { - width: 40px; - height: 40px; - align-self: end; - } - - &__link-text { - font-size: toRem(13px); - align-self: start; - } } diff --git a/client/src/components/icons/Cards.vue b/client/src/components/icons/Cards.vue index 60bfadbc..95d3ef14 100644 --- a/client/src/components/icons/Cards.vue +++ b/client/src/components/icons/Cards.vue @@ -3,7 +3,7 @@ xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" id="shape" - >cards + > -
Beitrag erfassen -
+ diff --git a/client/src/components/rooms/EntryCountWidget.vue b/client/src/components/rooms/EntryCountWidget.vue index 9fd5b4ed..58d0a6d7 100644 --- a/client/src/components/rooms/EntryCountWidget.vue +++ b/client/src/components/rooms/EntryCountWidget.vue @@ -1,11 +1,12 @@ diff --git a/client/src/components/ui/PopoverLink.vue b/client/src/components/ui/PopoverLink.vue index 4b7fa024..931e801c 100644 --- a/client/src/components/ui/PopoverLink.vue +++ b/client/src/components/ui/PopoverLink.vue @@ -1,7 +1,6 @@