Merged in feature/new-start-page (pull request #69)

Feature/new start page

Approved-by: Christian Cueni
This commit is contained in:
Ramon Wenger 2020-07-02 12:26:44 +00:00
commit 6238be62bc
45 changed files with 1571 additions and 539 deletions

View File

@ -16,6 +16,10 @@ definitions:
image: postgres image: postgres
variables: variables:
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
caches:
npm: $HOME/.npm
cypress: $HOME/.cache/Cypress
aliases: aliases:
- &lint - &lint
@ -39,6 +43,7 @@ aliases:
caches: caches:
- pip - pip
- node - node
- npm
artifacts: artifacts:
- client/cypress/**/*.png - client/cypress/**/*.png
- client/cypress/**/*.mp4 - client/cypress/**/*.mp4
@ -47,8 +52,8 @@ aliases:
script: script:
- echo "This pipeline rules!" - echo "This pipeline rules!"
- *setup-tests - *setup-tests
- npm install --prefix client - npm ci --prefix client
# - npm run "install:cypress" --prefix client - npm run "install:cypress" --prefix client
- psql -U $DATABASE_USER -h $DATABASE_HOST -c "create database $DATABASE_NAME" - psql -U $DATABASE_USER -h $DATABASE_HOST -c "create database $DATABASE_NAME"
- python server/manage.py dummy_data - python server/manage.py dummy_data
- python server/manage.py runserver & - python server/manage.py runserver &

View File

@ -0,0 +1,263 @@
{
"lohn-und-budget": {
"id": "TW9kdWxlTm9kZToyOA==",
"title": "Lohn und Budget",
"metaTitle": "Modul 1",
"teaser": "Die Berufsbildung ist ein neuer Lebensabschnit",
"intro": "\n <p>Sie stehen am Anfang eines neuen Lebensabschnitts. In Ihrer Rolle als Berufslernende oder Berufslernender haben Sie Verantwortung übernommen.</p>\n <p>Wie erging es Ihnen am ersten Arbeits- und Schultag?</p>\n ",
"slug": "lohn-und-budget",
"heroImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==",
"solutionsEnabled": false,
"bookmark": {
"note": null,
"__typename": "ModuleBookmarkNode"
},
"__typename": "ModuleNode",
"assignments": {
"edges": [
{
"node": {
"id": "QXNzaWdubWVudE5vZGU6MQ==",
"title": "Ein Auftragstitel",
"assignment": "Ein Auftrag",
"solution": null,
"submission": {
"id": "U3R1ZGVudFN1Ym1pc3Npb25Ob2RlOjE=",
"text": "Hir ist ein Feler gewesen",
"final": false,
"document": "",
"submissionFeedback": {
"id": "U3VibWlzc2lvbkZlZWRiYWNrTm9kZTox",
"text": "🙂😐🤬👍🤢🤢🤢🤢😮🤗",
"teacher": {
"firstName": "Nico",
"lastName": "Zickgraf",
"__typename": "UserNode"
},
"__typename": "SubmissionFeedbackNode"
},
"__typename": "StudentSubmissionNode"
},
"__typename": "AssignmentNode"
},
"__typename": "AssignmentNodeEdge"
}
],
"__typename": "AssignmentNodeConnection"
},
"objectiveGroups": {
"edges": [],
"__typename": "ObjectiveGroupNodeConnection"
},
"chapters": {
"edges": [
{
"node": {
"id": "Q2hhcHRlck5vZGU6MTg=",
"title": "1.1 Lehrbeginn",
"description": "Wie sieht Ihr Konsumverhalten aus?",
"bookmark": null,
"contentBlocks": {
"edges": [
{
"node": {
"id": "Q29udGVudEJsb2NrTm9kZToxOQ==",
"slug": "assignment",
"title": "Assignment",
"type": "NORMAL",
"contents": [
{
"type": "assignment",
"value": {
"title": "Ein Auftragstitel",
"assignment": "Ein Auftrag",
"id": "QXNzaWdubWVudE5vZGU6MQ=="
},
"id": "df8212ee-3e82-49fa-977e-c4b60789163e"
}
],
"userCreated": false,
"mine": false,
"bookmarks": [
],
"hiddenFor": {
"edges": [],
"__typename": "SchoolClassNodeConnection"
},
"visibleFor": {
"edges": [],
"__typename": "SchoolClassNodeConnection"
},
"__typename": "ContentBlockNode"
},
"__typename": "ContentBlockNodeEdge"
}
],
"__typename": "ContentBlockNodeConnection"
},
"__typename": "ChapterNode"
},
"__typename": "ChapterNodeEdge"
}
],
"__typename": "ChapterNodeConnection"
}
},
"geld": {
"id": "TW9kdWxlTm9kZTo0Mg==",
"title": "Geld",
"metaTitle": "Modul 2",
"teaser": " Geld braucht jeder von uns im t\u00e4glichen Leben.",
"intro": "\n <p>Jeder B\u00fcrger nutzt es. Nahezu jeden Tag. Kaum ein Tag vergeht, an dem wir nicht mit M\u00fcnzen oder Geldscheinen bezahlen, bargeldlose \u00dcberweisungen t\u00e4tigen oder andere Zahlungsmethoden verwenden. Doch was genau befindet sich da eigentlich in unserem Geldbeutel? Was ist das, was auf unseren Konten liegt und die Bezeichnung Geld tr\u00e4gt?</p>\n ",
"slug": "geld",
"heroImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==",
"solutionsEnabled": false,
"bookmark": null,
"__typename": "ModuleNode",
"assignments": {
"edges": [],
"__typename": "AssignmentNodeConnection"
},
"objectiveGroups": {
"edges": [],
"__typename": "ObjectiveGroupNodeConnection"
},
"chapters": {
"edges": [
{
"node": {
"id": "Q2hhcHRlck5vZGU6MzI=",
"title": "2.1 Eine Welt ohne Geld?",
"description": "",
"bookmark": null,
"contentBlocks": {
"edges": [
{
"node": {
"id": "Q29udGVudEJsb2NrTm9kZToxOQ==",
"slug": "assignment",
"title": "Assignment",
"type": "NORMAL",
"contents": [
{
"type": "assignment",
"value": {
"title": "Ein Auftragstitel",
"assignment": "Ein Auftrag",
"id": "QXNzaWdubWVudE5vZGU6MQ=="
},
"id": "df8212ee-3e82-49fa-977e-c4b60789163e"
}
],
"userCreated": false,
"mine": false,
"bookmarks": [
],
"hiddenFor": {
"edges": [],
"__typename": "SchoolClassNodeConnection"
},
"visibleFor": {
"edges": [],
"__typename": "SchoolClassNodeConnection"
},
"__typename": "ContentBlockNode"
},
"__typename": "ContentBlockNodeEdge"
}
],
"__typename": "ContentBlockNodeConnection"
},
"__typename": "ChapterNode"
},
"__typename": "ChapterNodeEdge"
}
],
"__typename": "ChapterNodeConnection"
}
},
"lerntipps": {
"id": "TW9kdWxlTm9kZTo3MA==",
"title": "Lerntipps",
"metaTitle": "Modul 4",
"teaser": "Lerntipps",
"intro": "\n <p>Sie stehen am Anfang eines neuen Lebensabschnitts. In Ihrer Rolle als Berufslernende oder Berufslernender haben Sie Verantwortung übernommen.</p>\n <p>Wie erging es Ihnen am ersten Arbeits- und Schultag?</p>\n ",
"slug": "lerntipps",
"heroImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==",
"solutionsEnabled": false,
"bookmark": {
"note": null,
"__typename": "ModuleBookmarkNode"
},
"__typename": "ModuleNode",
"assignments": {
"edges": [],
"__typename": "AssignmentNodeConnection"
},
"objectiveGroups": {
"edges": [],
"__typename": "ObjectiveGroupNodeConnection"
},
"chapters": {
"edges": [
{
"node": {
"id": "Q2hhcHRlck5vZGU6MTg=",
"title": "1.1 Lehrbeginn",
"description": "Wie sieht Ihr Konsumverhalten aus?",
"bookmark": null,
"contentBlocks": {
"edges": [],
"__typename": "ContentBlockNodeConnection"
},
"__typename": "ChapterNode"
},
"__typename": "ChapterNodeEdge"
}
],
"__typename": "ChapterNodeConnection"
}
},
"random": {
"id": "TW9kdWxlTm9kZTo1NA==",
"title": "Random",
"metaTitle": "Modul 5",
"teaser": "Random",
"intro": "\n <p>Sie stehen am Anfang eines neuen Lebensabschnitts. In Ihrer Rolle als Berufslernende oder Berufslernender haben Sie Verantwortung übernommen.</p>\n <p>Wie erging es Ihnen am ersten Arbeits- und Schultag?</p>\n ",
"slug": "random",
"heroImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==",
"solutionsEnabled": false,
"bookmark": {
"note": null,
"__typename": "ModuleBookmarkNode"
},
"__typename": "ModuleNode",
"assignments": {
"edges": [],
"__typename": "AssignmentNodeConnection"
},
"objectiveGroups": {
"edges": [],
"__typename": "ObjectiveGroupNodeConnection"
},
"chapters": {
"edges": [
{
"node": {
"id": "Q2hhcHRlck5vZGU6MTg=",
"title": "1.1 Lehrbeginn",
"description": "Wie sieht Ihr Konsumverhalten aus?",
"bookmark": null,
"contentBlocks": {
"edges": [],
"__typename": "ContentBlockNodeConnection"
},
"__typename": "ChapterNode"
},
"__typename": "ChapterNodeEdge"
}
],
"__typename": "ChapterNodeConnection"
}
}
}

View File

@ -0,0 +1,98 @@
{
"topic": {
"id": "VG9waWNOb2RlOjU=",
"title": "Geld und Kauf",
"teaser": "Die berufliche Grundbildung lehrt Sie, den Arbeitsalltag erfolgreich zu bew\u00e4ltigen, Ihre F\u00e4higkeiten zu entwickeln und beruflich flexibel zu sein. Ebenso wichtig ist der Umgang mit verschiedensten Mitmenschen. Eine angemessene m\u00fcndliche Kommunikation erleichtert das Zusammenleben und Zusammenarbeiten.",
"slug": "geld-und-kauf",
"description": "Deserunt in ut.\nAccusamus distinctio necessitatibus similique consequatur molestias. Sed magnam provident distinctio quia. Cumque repellat hic ipsum commodi.",
"vimeoId": null,
"instructions": null,
"modules": {
"edges": [
{
"node": {
"id": "TW9kdWxlTm9kZTo2",
"title": "Einleitung",
"metaTitle": "Video",
"teaser": "Die Berufsbildung ist ein neuer Lebensabschnit",
"intro": "\n <p>Sie stehen am Anfang eines neuen Lebensabschnitts. In Ihrer Rolle als Berufslernende oder Berufslernender haben Sie Verantwortung \u00fcbernommen.</p>\n <p>Wie erging es Ihnen am ersten Arbeits- und Schultag?</p>\n ",
"slug": "einleitung",
"heroImage": "",
"solutionsEnabled": false,
"topic": {
"slug": "geld-und-kauf",
"title": "Geld und Kauf",
"__typename": "TopicNode"
},
"bookmark": null,
"__typename": "ModuleNode"
},
"__typename": "ModuleNodeEdge"
},
{
"node": {
"id": "TW9kdWxlTm9kZToyOA==",
"title": "Lohn und Budget",
"metaTitle": "Modul 1",
"teaser": "Die Berufsbildung ist ein neuer Lebensabschnit",
"intro": "\n <p>Sie stehen am Anfang eines neuen Lebensabschnitts. In Ihrer Rolle als Berufslernende oder Berufslernender haben Sie Verantwortung \u00fcbernommen.</p>\n <p>Wie erging es Ihnen am ersten Arbeits- und Schultag?</p>\n ",
"slug": "lohn-und-budget",
"heroImage": "",
"solutionsEnabled": false,
"topic": {
"slug": "geld-und-kauf",
"title": "Geld und Kauf",
"__typename": "TopicNode"
},
"bookmark": null,
"__typename": "ModuleNode"
},
"__typename": "ModuleNodeEdge"
},
{
"node": {
"id": "TW9kdWxlTm9kZTo0Mg==",
"title": "Geld",
"metaTitle": "Modul 2",
"teaser": " Geld braucht jeder von uns im t\u00e4glichen Leben.",
"intro": "\n <p>Jeder B\u00fcrger nutzt es. Nahezu jeden Tag. Kaum ein Tag vergeht, an dem wir nicht mit M\u00fcnzen oder Geldscheinen bezahlen, bargeldlose \u00dcberweisungen t\u00e4tigen oder andere Zahlungsmethoden verwenden. Doch was genau befindet sich da eigentlich in unserem Geldbeutel? Was ist das, was auf unseren Konten liegt und die Bezeichnung Geld tr\u00e4gt?</p>\n ",
"slug": "geld",
"heroImage": "",
"solutionsEnabled": false,
"topic": {
"slug": "geld-und-kauf",
"title": "Geld und Kauf",
"__typename": "TopicNode"
},
"bookmark": null,
"__typename": "ModuleNode"
},
"__typename": "ModuleNodeEdge"
},
{
"node": {
"id": "TW9kdWxlTm9kZTo3MA==",
"title": "Lerntipps",
"metaTitle": "Modul 4",
"teaser": "Wie gehen Sie vor, wenn Sie sich auf eine Pr\u00fcfung vor-bereiten?",
"intro": "Ea ullam nam expedita voluptates consequuntur voluptates vitae. Earum eos iste sint mollitia ab.\nVoluptate autem commodi consequuntur enim magni. Incidunt temporibus voluptatibus numquam.",
"slug": "lerntipps",
"heroImage": "/media/original_images/dummy_PCmN1m5.jpg",
"solutionsEnabled": false,
"topic": {
"slug": "berufliche-grundbildung",
"title": "Berufliche Grundbildung",
"__typename": "TopicNode"
},
"bookmark": null,
"__typename": "ModuleNode"
},
"__typename": "ModuleNodeEdge"
}
],
"__typename": "ModuleNodeConnection"
},
"__typename": "TopicNode"
}
}

View File

@ -4,6 +4,7 @@
"pk": 5, "pk": 5,
"username": "rahel.cueni", "username": "rahel.cueni",
"email": "rahel.cueni@skillbox.example", "email": "rahel.cueni@skillbox.example",
"expiryDate": "3596153600",
"firstName": "Rahel", "firstName": "Rahel",
"lastName": "Cueni", "lastName": "Cueni",
"avatarUrl": "", "avatarUrl": "",
@ -17,6 +18,11 @@
"id": "U2Nob29sQ2xhc3NOb2RlOjI=", "id": "U2Nob29sQ2xhc3NOb2RlOjI=",
"__typename": "SchoolClassNode" "__typename": "SchoolClassNode"
}, },
"lastTopic": {
"id": "VG9waWNOb2RlOjU=",
"slug": "geld-und-kauf",
"__typename": "TopicNode"
},
"schoolClasses": { "schoolClasses": {
"edges": [ "edges": [
{ {

View File

@ -0,0 +1,70 @@
{
"TW9kdWxlTm9kZToyOA==": {
"id": "TW9kdWxlTm9kZToyOA==",
"title": "Lohn und Budget",
"metaTitle": "Modul 1",
"teaser": "Die Berufsbildung ist ein neuer Lebensabschnit",
"intro": "\n <p>Sie stehen am Anfang eines neuen Lebensabschnitts. In Ihrer Rolle als Berufslernende oder Berufslernender haben Sie Verantwortung \u00fcbernommen.</p>\n <p>Wie erging es Ihnen am ersten Arbeits- und Schultag?</p>\n ",
"slug": "lohn-und-budget",
"heroImage": "",
"solutionsEnabled": false,
"topic": {
"slug": "geld-und-kauf",
"title": "Geld und Kauf",
"__typename": "TopicNode"
},
"bookmark": null,
"__typename": "ModuleNode"
},
"TW9kdWxlTm9kZTo0Mg==": {
"id": "TW9kdWxlTm9kZTo0Mg==",
"title": "Geld",
"metaTitle": "Modul 2",
"teaser": " Geld braucht jeder von uns im t\u00e4glichen Leben.",
"intro": "\n <p>Jeder B\u00fcrger nutzt es. Nahezu jeden Tag. Kaum ein Tag vergeht, an dem wir nicht mit M\u00fcnzen oder Geldscheinen bezahlen, bargeldlose \u00dcberweisungen t\u00e4tigen oder andere Zahlungsmethoden verwenden. Doch was genau befindet sich da eigentlich in unserem Geldbeutel? Was ist das, was auf unseren Konten liegt und die Bezeichnung Geld tr\u00e4gt?</p>\n ",
"slug": "geld",
"heroImage": "",
"solutionsEnabled": false,
"topic": {
"slug": "geld-und-kauf",
"title": "Geld und Kauf",
"__typename": "TopicNode"
},
"bookmark": null,
"__typename": "ModuleNode"
},
"TW9kdWxlTm9kZTo3MA==": {
"id": "TW9kdWxlTm9kZTo3MA==",
"title": "Lerntipps",
"metaTitle": "Modul 4",
"teaser": "Wie gehen Sie vor, wenn Sie sich auf eine Pr\u00fcfung vor-bereiten?",
"intro": "Ea ullam nam expedita voluptates consequuntur voluptates vitae. Earum eos iste sint mollitia ab.\nVoluptate autem commodi consequuntur enim magni. Incidunt temporibus voluptatibus numquam.",
"slug": "lerntipps",
"heroImage": "",
"solutionsEnabled": false,
"topic": {
"slug": "geld-und-kauf",
"title": "Geld und Kauf",
"__typename": "TopicNode"
},
"bookmark": null,
"__typename": "ModuleNode"
},
"TW9kdWxlTm9kZTo1NA==": {
"id": "TW9kdWxlTm9kZTo1NA==",
"title": "Random",
"metaTitle": "Modul 5",
"teaser": "Wie gehen Sie vor, wenn Sie sich auf eine Pr\u00fcfung vor-bereiten?",
"intro": "Ea ullam nam expedita voluptates consequuntur voluptates vitae. Earum eos iste sint mollitia ab.\nVoluptate autem commodi consequuntur enim magni. Incidunt temporibus voluptatibus numquam.",
"slug": "random",
"heroImage": "",
"solutionsEnabled": false,
"topic": {
"slug": "geld-und-kauf",
"title": "Geld und Kauf",
"__typename": "TopicNode"
},
"bookmark": null,
"__typename": "ModuleNode"
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,27 @@
{
"topics": {
"edges": [
{
"node": {
"id": "VG9waWNOb2RlOjU=",
"order": 1,
"title": "Geld und Kauf",
"slug": "geld-und-kauf",
"__typename": "TopicNode"
},
"__typename": "TopicEdge"
},
{
"node": {
"id": "VG9waWNOb2RlOjUz",
"order": 2,
"title": "Berufliche Grundbildung",
"slug": "berufliche-grundbildung",
"__typename": "TopicNode"
},
"__typename": "TopicEdge"
}
],
"__typename": "TopicConnection"
}
}

View File

@ -4,7 +4,7 @@ describe('The Login Page', () => {
const password = 'test'; const password = 'test';
cy.visit('/beta-login'); cy.visit('/beta-login');
cy.login(username, password, true); cy.login(username, password, true);
cy.get('body').contains('Neues Wissen erwerben'); cy.assertStartPage();
}); });
it('user sees error message if username is omitted', () => { it('user sees error message if username is omitted', () => {
@ -35,7 +35,6 @@ describe('The Login Page', () => {
}); });
it('logs out then logs in again', () => { it('logs out then logs in again', () => {
const user = 'rahel.cueni'; const user = 'rahel.cueni';
const pw = 'test' const pw = 'test'

View File

@ -1,12 +1,12 @@
describe('Bookmarks', () => { describe('Bookmarks', () => {
beforeEach(() => { beforeEach(() => {
// todo: mock all the graphql queries and mutations // todo: mock all the graphql queries and mutations
cy.exec("python ../server/manage.py prepare_bookmarks_for_cypress"); cy.exec('python ../server/manage.py prepare_bookmarks_for_cypress');
cy.viewport('macbook-15'); cy.viewport('macbook-15');
cy.startGraphQLCapture(); cy.startGraphQLCapture();
cy.login('rahel.cueni', 'test', true); cy.login('rahel.cueni', 'test', true);
cy.get('body').contains('Neues Wissen erwerben'); cy.assertStartPage();
}); });
it('should bookmark content block', () => { it('should bookmark content block', () => {
@ -35,5 +35,4 @@ describe('Bookmarks', () => {
cy.get('[data-cy=modal-save-button]').click(); cy.get('[data-cy=modal-save-button]').click();
}); });
}); });

View File

@ -1,4 +1,4 @@
import { GraphQLError } from "graphql"; import { GraphQLError } from 'graphql';
const schema = require('../fixtures/schema.json'); const schema = require('../fixtures/schema.json');
@ -20,23 +20,21 @@ describe('Email Verifcation', () => {
} }
}); });
cy.login('rahel.cueni', 'test', true) cy.login('rahel.cueni', 'test', true)
cy.get('[data-cy="rooms-link"]').contains('Alle Räume anzeigen'); cy.assertStartPage();
cy.visit('/license-activation'); cy.visit('/license-activation');
cy.redeemCoupon('12345asfd'); cy.redeemCoupon('12345asfd');
cy.get('body').contains('Neues Wissen erwerben'); cy.assertStartPage();
}); });
it('displays error if input is missing', () => { it('displays error if input is missing', () => {
cy.viewport('macbook-15'); cy.viewport('macbook-15');
cy.login('rahel.cueni', 'test', true) cy.login('rahel.cueni', 'test', true)
cy.get('[data-cy="rooms-link"]').contains('Alle Räume anzeigen'); cy.assertStartPage();
cy.visit('/license-activation'); cy.visit('/license-activation');
cy.redeemCoupon(''); cy.redeemCoupon('');
cy.get('[data-cy="coupon-local-errors"]').contains('Coupon ist ein Pflichtfeld.'); cy.get('[data-cy="coupon-local-errors"]').contains('Coupon ist ein Pflichtfeld.');
}); });
it('displays error if coupon input is wrong', () => { it('displays error if coupon input is wrong', () => {
@ -48,12 +46,11 @@ describe('Email Verifcation', () => {
} }
}); });
cy.login('rahel.cueni', 'test', true) cy.login('rahel.cueni', 'test', true)
cy.get('[data-cy="rooms-link"]').contains('Alle Räume anzeigen'); cy.assertStartPage();
cy.visit('/license-activation'); cy.visit('/license-activation');
cy.redeemCoupon('12345asfd'); cy.redeemCoupon('12345asfd');
cy.get('[data-cy="coupon-remote-errors"]').contains('Der angegebene Coupon-Code ist ungültig.'); cy.get('[data-cy="coupon-remote-errors"]').contains('Der angegebene Coupon-Code ist ungültig.');
}); });
it('displays error if an error occures', () => { it('displays error if an error occures', () => {
@ -61,16 +58,14 @@ describe('Email Verifcation', () => {
cy.mockGraphql({ cy.mockGraphql({
schema: schema, schema: schema,
operations: { operations: {
Coupon: new GraphQLError("unknown_error") Coupon: new GraphQLError('unknown_error')
} }
}); });
cy.login('rahel.cueni', 'test', true) cy.login('rahel.cueni', 'test', true)
cy.get('[data-cy="rooms-link"]').contains('Alle Räume anzeigen'); cy.assertStartPage();
cy.visit('/license-activation'); cy.visit('/license-activation');
cy.redeemCoupon('12345asfd'); cy.redeemCoupon('12345asfd');
cy.get('[data-cy="coupon-remote-errors"]').contains('Es ist ein Fehler aufgetreten. Bitte versuchen Sie es nochmals oder kontaktieren Sie den Administrator.'); cy.get('[data-cy="coupon-remote-errors"]').contains('Es ist ein Fehler aufgetreten. Bitte versuchen Sie es nochmals oder kontaktieren Sie den Administrator.');
}); });
}); });

View File

@ -1,12 +1,65 @@
const schema = require('../fixtures/schema.json'); const schema = require('../fixtures/schema.json');
const assignments = require('../fixtures/assignments.json'); const assignments = require('../fixtures/assignments.json');
const lohnModule = require('../fixtures/module.json'); const mePayload = require('../fixtures/me.join-class.json');
const geldModule = require('../fixtures/module-geld.json'); const topics = require('../fixtures/topics.json');
const baseTopic = require('../fixtures/geld-und-kauf.json');
const moduleTeasers = require('../fixtures/module-teasers.json');
const fullModules = require('../fixtures/full-modules.json');
const topic = {
topic: {
...baseTopic.topic,
modules: {
'__typename': 'ModuleNodeConnection',
edges: [
...Object.values(moduleTeasers).map(module => {
return {
node: module,
__typename: 'ModuleNodeEdge'
}
})
]
}
}
}
Cypress.Commands.add('checkHome', (n, skipHome) => {
if (!skipHome) {
cy.get('[data-cy="home-link"]').click();
}
cy.get('[data-cy=start-modules-list]').should('exist');
cy.get('[data-cy=start-module-teaser]').should('have.length', n);
});
Cypress.Commands.add('goToModule', (topicTitle, moduleMetaTitle) => {
cy.get('[data-cy=open-sidebar-link]').click();
cy.contains(topicTitle).click();
cy.get('[data-cy=topic-title]').should('exist').should('contain', topicTitle);
cy.contains(moduleMetaTitle).click();
});
describe('Current Module', () => { describe('Current Module', () => {
before(() => { before(() => {
cy.server(); cy.server();
let me = {
...mePayload.me,
lastModule: {
// 'id': 'TW9kdWxlTm9kZToxNw==',
'slug': 'lohn-und-budget',
'__typename': 'ModuleNode'
},
lastTopic: {
'id': 'VG9waWNOb2RlOjU=',
'slug': 'geld-und-kauf',
'__typename': 'TopicNode'
},
recentModules: {
'edges': [],
'__typename': 'ModuleNodeConnection'
},
};
cy.mockGraphql({ cy.mockGraphql({
schema: schema, schema: schema,
// endpoint: '/api/graphql' // endpoint: '/api/graphql'
@ -14,16 +67,7 @@ describe('Current Module', () => {
MeQuery: variables => { MeQuery: variables => {
return { return {
me: { me: {
'lastModule': { ...me,
// 'id': 'TW9kdWxlTm9kZToxNw==',
'slug': 'lohn-und-budget',
'__typename': 'ModuleNode'
},
'lastTopic': {
'id': 'VG9waWNOb2RlOjU=',
'slug': 'geld-und-kauf',
'__typename': 'TopicNode'
},
'__typename': 'UserNode', '__typename': 'UserNode',
'permissions': [] 'permissions': []
} }
@ -33,28 +77,22 @@ describe('Current Module', () => {
assignments assignments
}, },
ModulesQuery: variables => { ModulesQuery: variables => {
let module;
if (variables.slug === 'lohn-und-budget') {
module = lohnModule;
} else {
module = geldModule
}
return { return {
module module: fullModules[variables.slug]
}
},
TopicsQuery: topics,
Topic: topic,
UpdateLastTopic: {
'updateLastTopic': {
'topic': topic.topic,
'__typename': 'UpdateLastTopicPayload'
} }
}, },
UpdateLastModule: variables => { UpdateLastModule: variables => {
let module;
if (variables.input.id === 'TW9kdWxlTm9kZToxNw==') {
module = lohnModule
} else {
module = geldModule
}
return { return {
updateLastModule: { updateLastModule: {
module, lastModule: moduleTeasers[variables.input.id],
errors: null,
__typename: 'UpdateLastModulePayload' __typename: 'UpdateLastModulePayload'
} }
} }
@ -67,21 +105,50 @@ describe('Current Module', () => {
cy.viewport('macbook-15'); cy.viewport('macbook-15');
cy.apolloLogin('nico.zickgraf', 'test'); cy.apolloLogin('nico.zickgraf', 'test');
cy.visit('/book/topic/geld-und-kauf'); cy.visit('/');
cy.contains('Modul 1').click();
cy.get('[data-cy=module-title]').should('contain', 'Lohn und Budget'); // module list exists, but does not have anything in it
cy.checkHome(0, true);
cy.get('[data-cy=no-modules-yet]').should('exist').should('contain', 'Sie haben sich noch kein Modul angeschaut. Legen Sie jetzt los!');
cy.get('[data-cy="home-link"]').click(); cy.goToModule('Geld und Kauf', 'Modul 2');
cy.get('[data-cy="current-module-link"]').click();
cy.get('[data-cy=module-title]').should('contain', 'Lohn und Budget');
cy.visit('/book/topic/geld-und-kauf');
cy.contains('Modul 2').click();
cy.get('[data-cy=module-title]').should('contain', 'Geld'); cy.get('[data-cy=module-title]').should('contain', 'Geld');
cy.checkHome(1);
cy.get('[data-cy=start-module-teaser]').first().should('contain', 'Geld');
cy.get('[data-cy="home-link"]').click(); cy.goToModule('Geld und Kauf', 'Modul 1');
cy.get('[data-cy="current-module-link"]').click(); cy.get('[data-cy=module-title]').should('contain', 'Lohn und Budget');
cy.get('[data-cy=module-title]').should('contain', 'Geld') cy.checkHome(2);
cy.get('[data-cy=start-module-teaser]').first().should('contain', 'Lohn und Budget');
cy.get('[data-cy=start-module-teaser]').eq(1).should('contain', 'Geld');
cy.goToModule('Geld und Kauf', 'Modul 4');
cy.get('[data-cy=module-title]').should('contain', 'Lerntipps');
cy.checkHome(3);
cy.get('[data-cy=start-module-teaser]').first().should('contain', 'Lerntipps');
cy.get('[data-cy=start-module-teaser]').eq(1).should('contain', 'Lohn und Budget');
cy.get('[data-cy=start-module-teaser]').eq(2).should('contain', 'Geld');
// module list is full, should switch only the order around
cy.goToModule('Geld und Kauf', 'Modul 2');
cy.get('[data-cy=module-title]').should('contain', 'Geld');
cy.checkHome(3);
cy.get('[data-cy=start-module-teaser]').first().should('contain', 'Geld');
cy.get('[data-cy=start-module-teaser]').eq(1).should('contain', 'Lerntipps');
cy.get('[data-cy=start-module-teaser]').eq(2).should('contain', 'Lohn und Budget');
cy.goToModule('Geld und Kauf', 'Modul 5');
cy.get('[data-cy=module-title]').should('contain', 'Random');
cy.checkHome(3);
cy.get('[data-cy=start-module-teaser]').first().should('contain', 'Random');
cy.get('[data-cy=start-module-teaser]').eq(1).should('contain', 'Geld');
cy.get('[data-cy=start-module-teaser]').eq(2).should('contain', 'Lerntipps');
cy.get('[data-cy=start-module-teaser]').last().click();
cy.get('[data-cy=module-title]').should('contain', 'Lerntipps');
cy.checkHome(3);
cy.get('[data-cy=start-module-teaser]').first().should('contain', 'Lerntipps');
cy.get('[data-cy=start-module-teaser]').eq(1).should('contain', 'Random');
cy.get('[data-cy=start-module-teaser]').eq(2).should('contain', 'Geld');
}) })
}); });

View File

@ -29,7 +29,6 @@
// import 'cypress-graphql-mock'; // import 'cypress-graphql-mock';
import '@iam4x/cypress-graphql-mock'; import '@iam4x/cypress-graphql-mock';
Cypress.Commands.add('apolloLogin', (username, password) => { Cypress.Commands.add('apolloLogin', (username, password) => {
const payload = { const payload = {
'operationName': 'BetaLogin', 'operationName': 'BetaLogin',
@ -47,12 +46,10 @@ Cypress.Commands.add('apolloLogin', (username, password) => {
url: '/api/graphql-public/', url: '/api/graphql-public/',
body: payload body: payload
}); });
}); });
// todo: replace with apollo call // todo: replace with apollo call
Cypress.Commands.add("login", (username, password, visitLogin = false) => { Cypress.Commands.add('login', (username, password, visitLogin = false) => {
if (visitLogin) { if (visitLogin) {
cy.visit('/beta-login'); cy.visit('/beta-login');
} }
@ -67,7 +64,7 @@ Cypress.Commands.add("login", (username, password, visitLogin = false) => {
cy.get('[data-cy=login-button]').click(); cy.get('[data-cy=login-button]').click();
}); });
Cypress.Commands.add("logout", () => { Cypress.Commands.add('logout', () => {
cy.get('[data-cy=user-icon]').click(); cy.get('[data-cy=user-icon]').click();
cy.get('[data-cy=logout]').click(); cy.get('[data-cy=logout]').click();
}); });
@ -122,7 +119,6 @@ Cypress.Commands.add('enterPassword', (password) => {
}); });
Cypress.Commands.add('register', (prefix, firstname, lastname, street, city, postcode, password, passwordConfirmation, acceptTerms) => { Cypress.Commands.add('register', (prefix, firstname, lastname, street, city, postcode, password, passwordConfirmation, acceptTerms) => {
let selection = prefix === 1 ? 'Herr' : 'Frau'; let selection = prefix === 1 ? 'Herr' : 'Frau';
cy.get('[data-cy="prefix-selection"]').select(selection); cy.get('[data-cy="prefix-selection"]').select(selection);
@ -153,7 +149,7 @@ Cypress.Commands.add('register', (prefix, firstname, lastname, street, city, pos
if (acceptTerms) { if (acceptTerms) {
cy.get('[data-cy="acceptedTerms-input"] > input').first().check({force: true}).then(() => { cy.get('[data-cy="acceptedTerms-input"] > input').first().check({force: true}).then(() => {
cy.get('[data-cy="acceptedTerms-input"] > input:checkbox').should('be.checked'); cy.get('[data-cy="acceptedTerms-input"] > input:checkbox').should('be.checked');
});; });
} }
cy.get('[data-cy="passwordConfirmation-input"]').type(passwordConfirmation); cy.get('[data-cy="passwordConfirmation-input"]').type(passwordConfirmation);
@ -167,3 +163,6 @@ Cypress.Commands.add('redeemCoupon', coupon => {
cy.get('[data-cy="coupon-button"]').click(); cy.get('[data-cy="coupon-button"]').click();
}) })
Cypress.Commands.add('assertStartPage', () => {
cy.get('[data-cy=start-modules-list]').should('exist');
});

311
client/package-lock.json generated
View File

@ -2514,6 +2514,86 @@
} }
} }
}, },
"@cypress/request": {
"version": "2.88.5",
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.5.tgz",
"integrity": "sha512-TzEC1XMi1hJkywWpRfD2clreTa/Z+lOrXDCxxBTBPEcY5azdPi56A6Xw+O4tWJnaJH3iIE7G5aDXZC6JgRZLcA==",
"dev": true,
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.2",
"har-validator": "~5.1.3",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"oauth-sign": "~0.9.0",
"performance-now": "^2.1.0",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
},
"dependencies": {
"ajv": {
"version": "6.12.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
"integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"har-validator": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
"dev": true,
"requires": {
"ajv": "^6.5.5",
"har-schema": "^2.0.0"
}
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
"dev": true
},
"tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"dev": true,
"requires": {
"psl": "^1.1.28",
"punycode": "^2.1.1"
}
}
}
},
"@cypress/xvfb": { "@cypress/xvfb": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz",
@ -2969,6 +3049,12 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.1.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.1.tgz",
"integrity": "sha512-aK9jxMypeSrhiYofWWBf/T7O+KwaiAHzM4sveCdWPn71lzUSMimRnKzhXDKfKwV1kWoBo2P1aGgaIYGLf9/ljw==" "integrity": "sha512-aK9jxMypeSrhiYofWWBf/T7O+KwaiAHzM4sveCdWPn71lzUSMimRnKzhXDKfKwV1kWoBo2P1aGgaIYGLf9/ljw=="
}, },
"@types/sinonjs__fake-timers": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz",
"integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==",
"dev": true
},
"@types/sizzle": { "@types/sizzle": {
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",
@ -3395,9 +3481,9 @@
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
}, },
"arch": { "arch": {
"version": "2.1.1", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz", "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.2.tgz",
"integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==", "integrity": "sha512-NTBIIbAfkJeIletyABbVtdPgeKfDafR+1mZV/AyyfC1UkVkp9iUjV+wwmqtUgphHYajbI86jejBJp5e+jkGTiQ==",
"dev": true "dev": true
}, },
"are-we-there-yet": { "are-we-there-yet": {
@ -7032,41 +7118,42 @@
"integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=" "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA="
}, },
"cypress": { "cypress": {
"version": "4.2.0", "version": "4.9.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-4.2.0.tgz", "resolved": "https://registry.npmjs.org/cypress/-/cypress-4.9.0.tgz",
"integrity": "sha512-8LdreL91S/QiTCLYLNbIjLL8Ht4fJmu/4HGLxUI20Tc7JSfqEfCmXELrRfuPT0kjosJwJJZacdSji9XSRkPKUw==", "integrity": "sha512-qGxT5E0j21FPryzhb0OBjCdhoR/n1jXtumpFFSBPYWsaZZhNaBvc3XlBUDEZKkkXPsqUFYiyhWdHN/zo0t5FcA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@cypress/listr-verbose-renderer": "0.4.1", "@cypress/listr-verbose-renderer": "0.4.1",
"@cypress/request": "2.88.5",
"@cypress/xvfb": "1.2.4", "@cypress/xvfb": "1.2.4",
"@types/sinonjs__fake-timers": "6.0.1",
"@types/sizzle": "2.3.2", "@types/sizzle": "2.3.2",
"arch": "2.1.1", "arch": "2.1.2",
"bluebird": "3.7.2", "bluebird": "3.7.2",
"cachedir": "2.3.0", "cachedir": "2.3.0",
"chalk": "2.4.2", "chalk": "2.4.2",
"check-more-types": "2.24.0", "check-more-types": "2.24.0",
"cli-table3": "0.5.1", "cli-table3": "0.5.1",
"commander": "4.1.0", "commander": "4.1.1",
"common-tags": "1.8.0", "common-tags": "1.8.0",
"debug": "4.1.1", "debug": "4.1.1",
"eventemitter2": "4.1.2", "eventemitter2": "6.4.2",
"execa": "1.0.0", "execa": "1.0.0",
"executable": "4.1.1", "executable": "4.1.1",
"extract-zip": "1.6.7", "extract-zip": "1.7.0",
"fs-extra": "8.1.0", "fs-extra": "8.1.0",
"getos": "3.1.4", "getos": "3.2.1",
"is-ci": "2.0.0", "is-ci": "2.0.0",
"is-installed-globally": "0.1.0", "is-installed-globally": "0.3.2",
"lazy-ass": "1.6.0", "lazy-ass": "1.6.0",
"listr": "0.14.3", "listr": "0.14.3",
"lodash": "4.17.15", "lodash": "4.17.15",
"log-symbols": "3.0.0", "log-symbols": "3.0.0",
"minimist": "1.2.2", "minimist": "1.2.5",
"moment": "2.24.0", "moment": "2.26.0",
"ospath": "1.2.2", "ospath": "1.2.2",
"pretty-bytes": "5.3.0", "pretty-bytes": "5.3.0",
"ramda": "0.26.1", "ramda": "0.26.1",
"request": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16",
"request-progress": "3.0.0", "request-progress": "3.0.0",
"supports-color": "7.1.0", "supports-color": "7.1.0",
"tmp": "0.1.0", "tmp": "0.1.0",
@ -7075,18 +7162,6 @@
"yauzl": "2.10.0" "yauzl": "2.10.0"
}, },
"dependencies": { "dependencies": {
"ajv": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
"integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"bluebird": { "bluebird": {
"version": "3.7.2", "version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@ -7116,9 +7191,9 @@
} }
}, },
"commander": { "commander": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw==", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true "dev": true
}, },
"cross-spawn": { "cross-spawn": {
@ -7158,12 +7233,6 @@
"strip-eof": "^1.0.0" "strip-eof": "^1.0.0"
} }
}, },
"fast-deep-equal": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==",
"dev": true
},
"get-stream": { "get-stream": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
@ -7187,22 +7256,6 @@
"path-is-absolute": "^1.0.0" "path-is-absolute": "^1.0.0"
} }
}, },
"har-validator": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
"dev": true,
"requires": {
"ajv": "^6.5.5",
"har-schema": "^2.0.0"
}
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"lodash": { "lodash": {
"version": "4.17.15", "version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
@ -7219,9 +7272,15 @@
} }
}, },
"minimist": { "minimist": {
"version": "1.2.2", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.2.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-rIqbOrKb8GJmx/5bc2M0QchhUouMXSpd1RTclXsB41JdL+VtnojfaJR+h7F9k18/4kHUsBFgk80Uk+q569vjPA==", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"moment": {
"version": "2.26.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.26.0.tgz",
"integrity": "sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==",
"dev": true "dev": true
}, },
"ms": { "ms": {
@ -7230,12 +7289,6 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true "dev": true
}, },
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
"dev": true
},
"pump": { "pump": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@ -7246,33 +7299,6 @@
"once": "^1.3.1" "once": "^1.3.1"
} }
}, },
"request": {
"version": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16",
"from": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16",
"dev": true,
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.2",
"har-validator": "~5.1.3",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"oauth-sign": "~0.9.0",
"performance-now": "^2.1.0",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
}
},
"rimraf": { "rimraf": {
"version": "2.7.1", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@ -7307,16 +7333,6 @@
"requires": { "requires": {
"rimraf": "^2.6.3" "rimraf": "^2.6.3"
} }
},
"tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"dev": true,
"requires": {
"psl": "^1.1.28",
"punycode": "^2.1.1"
}
} }
} }
}, },
@ -8348,9 +8364,9 @@
} }
}, },
"eventemitter2": { "eventemitter2": {
"version": "4.1.2", "version": "6.4.2",
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-4.1.2.tgz", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.2.tgz",
"integrity": "sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU=", "integrity": "sha512-r/Pwupa5RIzxIHbEKCkNXqpEQIIT4uQDxmP4G/Lug/NokVUWj0joz/WzWl3OxRpC5kDrH/WdiUJoR+IrwvXJEw==",
"dev": true "dev": true
}, },
"eventemitter3": { "eventemitter3": {
@ -8705,15 +8721,15 @@
} }
}, },
"extract-zip": { "extract-zip": {
"version": "1.6.7", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz",
"integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==",
"dev": true, "dev": true,
"requires": { "requires": {
"concat-stream": "1.6.2", "concat-stream": "^1.6.2",
"debug": "2.6.9", "debug": "^2.6.9",
"mkdirp": "0.5.1", "mkdirp": "^0.5.4",
"yauzl": "2.4.1" "yauzl": "^2.10.0"
}, },
"dependencies": { "dependencies": {
"debug": { "debug": {
@ -8725,13 +8741,19 @@
"ms": "2.0.0" "ms": "2.0.0"
} }
}, },
"yauzl": { "minimist": {
"version": "2.4.1", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"fd-slicer": "~1.0.1" "minimist": "^1.2.5"
} }
} }
} }
@ -8779,9 +8801,9 @@
} }
}, },
"fd-slicer": { "fd-slicer": {
"version": "1.0.1", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
"integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
"dev": true, "dev": true,
"requires": { "requires": {
"pend": "~1.2.0" "pend": "~1.2.0"
@ -9072,9 +9094,9 @@
}, },
"dependencies": { "dependencies": {
"graceful-fs": { "graceful-fs": {
"version": "4.2.3", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"dev": true "dev": true
}, },
"jsonfile": { "jsonfile": {
@ -9685,12 +9707,12 @@
"integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
}, },
"getos": { "getos": {
"version": "3.1.4", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/getos/-/getos-3.1.4.tgz", "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz",
"integrity": "sha512-UORPzguEB/7UG5hqiZai8f0vQ7hzynMQyJLxStoQ8dPGAcmgsfXOPA4iE/fGtweHYkK+z4zc9V0g+CIFRf5HYw==", "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"async": "^3.1.0" "async": "^3.2.0"
}, },
"dependencies": { "dependencies": {
"async": { "async": {
@ -9783,12 +9805,12 @@
} }
}, },
"global-dirs": { "global-dirs": {
"version": "0.1.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz",
"integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", "integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==",
"dev": true, "dev": true,
"requires": { "requires": {
"ini": "^1.3.4" "ini": "^1.3.5"
} }
}, },
"globals": { "globals": {
@ -10705,13 +10727,21 @@
} }
}, },
"is-installed-globally": { "is-installed-globally": {
"version": "0.1.0", "version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz",
"integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==",
"dev": true, "dev": true,
"requires": { "requires": {
"global-dirs": "^0.1.0", "global-dirs": "^2.0.1",
"is-path-inside": "^1.0.0" "is-path-inside": "^3.0.1"
},
"dependencies": {
"is-path-inside": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz",
"integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==",
"dev": true
}
} }
}, },
"is-number": { "is-number": {
@ -17915,9 +17945,9 @@
} }
}, },
"rxjs": { "rxjs": {
"version": "6.5.4", "version": "6.5.5",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz",
"integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"tslib": "^1.9.0" "tslib": "^1.9.0"
@ -20729,17 +20759,6 @@
"requires": { "requires": {
"buffer-crc32": "~0.2.3", "buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0" "fd-slicer": "~1.1.0"
},
"dependencies": {
"fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
"integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
"dev": true,
"requires": {
"pend": "~1.2.0"
}
}
} }
}, },
"zen-observable": { "zen-observable": {

View File

@ -110,7 +110,7 @@
"babel-core": "^7.0.0-bridge.0", "babel-core": "^7.0.0-bridge.0",
"babel-jest": "^24.8.0", "babel-jest": "^24.8.0",
"canvas": "^2.5.0", "canvas": "^2.5.0",
"cypress": "^4.2.0", "cypress": "^4.9.0",
"jest": "^24.8.0", "jest": "^24.8.0",
"jest-serializer-vue": "^2.0.2", "jest-serializer-vue": "^2.0.2",
"jest-transform-graphql": "^2.1.0", "jest-transform-graphql": "^2.1.0",

View File

@ -18,7 +18,6 @@
<script> <script>
import DefaultLayout from '@/layouts/DefaultLayout'; import DefaultLayout from '@/layouts/DefaultLayout';
import SimpleLayout from '@/layouts/SimpleLayout'; import SimpleLayout from '@/layouts/SimpleLayout';
import BlankLayout from '@/layouts/BlankLayout';
import FullScreenLayout from '@/layouts/FullScreenLayout'; import FullScreenLayout from '@/layouts/FullScreenLayout';
import PublicLayout from '@/layouts/PublicLayout'; import PublicLayout from '@/layouts/PublicLayout';
import Modal from '@/components/Modal'; import Modal from '@/components/Modal';
@ -47,7 +46,6 @@
ScrollUp, ScrollUp,
DefaultLayout, DefaultLayout,
SimpleLayout, SimpleLayout,
BlankLayout,
FullScreenLayout, FullScreenLayout,
PublicLayout, PublicLayout,
Modal, Modal,
@ -87,6 +85,7 @@
body { body {
overflow-y: auto; overflow-y: auto;
overflow-x: hidden;
height: 100vh; height: 100vh;
} }

View File

@ -2,6 +2,7 @@
<header class="header-bar"> <header class="header-bar">
<a <a
class="header-bar__sidebar-link" class="header-bar__sidebar-link"
data-cy="open-sidebar-link"
@click="openSidebar('navigation')"> @click="openSidebar('navigation')">
<hamburger class="header-bar__sidebar-icon"/> <hamburger class="header-bar__sidebar-icon"/>
</a> </a>

View File

@ -1,103 +0,0 @@
<template>
<div class="news-teasers">
<div
:key="teaser.id"
class="news-teasers__teaser teaser"
v-for="teaser in newsTeasers">
<a :href="teaser.newsArticleUrl">
<img
:src="teaser.imageUrl"
class="teaser__image">
<p class="teaser__image-source">
<a
:href="teaser.imageSource"
class="tiny-text">Quelle {{ teaser.imageSource }}</a></p>
<h4 class="teaser__title">{{ teaser.title }}</h4>
<p class="teaser__description">{{ teaser.description }}</p>
<p class="teaser__date">{{ teaser.displayDate }}</p>
</a>
</div>
</div>
</template>
<script>
import NEWS_TEASER_QUERY from '@/graphql/gql/newsTeasersQuery.gql';
export default {
components: {},
data() {
return {
newsTeasers: []
};
},
apollo: {
$client: 'publicClient',
newsTeasers: {
query: NEWS_TEASER_QUERY,
update(data) {
return this.$getRidOfEdges(data).newsTeasers;
}
}
}
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_functions.scss";
@import "@/styles/_mixins.scss";
$news_width: 550px;
$image_height: 254px;
.teasers {
display: block;
@include desktop {
@supports (display: grid) {
display: grid;
display: -ms-grid;
}
grid-template-columns: repeat(auto-fit, minmax(320px, $news_width));
grid-gap: 40px;
grid-auto-rows: minmax(400px, auto);
grid-template-rows: auto auto;
-ms-grid-columns: $news_width $news_width;
}
}
.teaser {
margin-bottom: $large-spacing;
position: relative;
&__image {
display: block;
max-width: 100%;
height: auto;
@include desktop {
max-width: $news_width;
}
}
&__image-source {
line-height: 25px;
}
&__description {
margin-bottom: $large-spacing;
}
&__date {
font-family: $sans-serif-font-family;
font-weight: $font-weight-regular;
color: $color-silver-dark;
position: absolute;
bottom: 0;
left: 0;
}
}
</style>

View File

@ -56,6 +56,7 @@
cursor: pointer; cursor: pointer;
background-color: $color-white; background-color: $color-white;
border: 1px solid $color-silver; border: 1px solid $color-silver;
z-index: 2;
&__icon { &__icon {
width: 50px; width: 50px;

View File

@ -90,20 +90,6 @@
Logo Logo
}, },
computed: {
topicRoute() {
if (this.me.lastTopic && this.me.lastTopic.slug) {
return {
name: 'topic',
params: {
topicSlug: this.me.lastTopic.slug
}
}
}
return '/book/topic/berufliche-grundbildung'
}
},
methods: { methods: {
isActive(linkName) { isActive(linkName) {
return linkName === 'book' && this.$route.path.indexOf('module') > -1; return linkName === 'book' && this.$route.path.indexOf('module') > -1;

View File

@ -52,6 +52,7 @@
import {withoutOwnerFirst} from '@/helpers/sorting'; import {withoutOwnerFirst} from '@/helpers/sorting';
import BookmarkActions from '@/components/notes/BookmarkActions'; import BookmarkActions from '@/components/notes/BookmarkActions';
import meMixin from '@/mixins/me';
export default { export default {
@ -65,20 +66,15 @@
default: false default: false
} }
}, },
mixins: [meMixin],
components: { components: {
BookmarkActions, BookmarkActions,
ObjectiveGroups, ObjectiveGroups,
Chapter Chapter
}, },
data() {
return {
me: {
permissions: []
}
}
},
computed: { computed: {
languageCommunicationObjectiveGroups() { languageCommunicationObjectiveGroups() {
return this.module.objectiveGroups ? this.module.objectiveGroups return this.module.objectiveGroups ? this.module.objectiveGroups
@ -114,11 +110,26 @@
id: moduleId id: moduleId
} }
}, },
update(store, {data: {updateLastModule: {module}}}) { update(store, {data: {updateLastModule: {lastModule}}}) {
if (module) { if (lastModule) {
const data = store.readQuery({query: ME_QUERY}); const data = store.readQuery({query: ME_QUERY});
if (data) { if (data) {
data.me.lastModule = module; data.me.lastModule = lastModule;
let recentModules = data.me.recentModules.edges;
let newRecentModules;
let index = recentModules.findIndex(element => element.node.id === lastModule.id);
if (index > -1) {
newRecentModules = [...recentModules.slice(0, index), ...recentModules.slice(index + 1)]
} else if (recentModules.length >= 3) {
newRecentModules = recentModules.slice(0, recentModules.length - 1);
} else {
newRecentModules = recentModules;
}
newRecentModules.unshift({
__typename: 'ModuleNodeEdge',
node: lastModule
});
data.me.recentModules.edges = newRecentModules;
store.writeQuery({query: ME_QUERY, data}); store.writeQuery({query: ME_QUERY, data});
} }
} }
@ -199,12 +210,6 @@
this.$store.dispatch('editNote', this.module.bookmark.note); this.$store.dispatch('editNote', this.module.bookmark.note);
}, },
}, },
apollo: {
me: {
query: ME_QUERY,
}
},
} }
</script> </script>

View File

@ -1,8 +1,9 @@
<template> <template>
<router-link <router-link
:to="moduleLink" :to="moduleLink"
:class="['module-teaser', {'module-teaser--small': !teaser}]"
tag="div" tag="div"
class="module-teaser"> >
<div <div
:style="{backgroundImage: 'url('+heroImage+')'}" :style="{backgroundImage: 'url('+heroImage+')'}"
class="module-teaser__image"/> class="module-teaser__image"/>
@ -46,6 +47,10 @@
box-sizing: border-box; box-sizing: border-box;
cursor: pointer; cursor: pointer;
&--small {
height: 300px;
}
&__image { &__image {
width: 100%; width: 100%;
max-height: 150px; max-height: 150px;

View File

@ -0,0 +1,67 @@
<template>
<div class="news-teaser">
<a :href="teaser.newsArticleUrl">
<img
:src="teaser.imageUrl"
class="news-teaser__image">
<p class="news-teaser__image-source">
<a
:href="teaser.imageSource"
class="tiny-text">Quelle {{ teaser.imageSource }}</a></p>
<h4 class="news-teaser__title">{{ teaser.title }}</h4>
<p class="news-teaser__description">{{ teaser.description }}</p>
<p class="news-teaser__date">{{ teaser.displayDate }}</p>
</a>
</div>
</template>
<script>
export default {
props: {
teaser: {
type: Object,
default: () => {
return {}
}
}
}
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_functions.scss";
@import "@/styles/_mixins.scss";
.news-teaser {
position: relative;
padding-bottom: $large-spacing;
&__image {
display: block;
max-width: 100%;
height: auto;
@include desktop {
max-width: $news_width;
}
}
&__image-source {
line-height: 25px;
}
&__description {
margin-bottom: $large-spacing;
}
&__date {
font-family: $sans-serif-font-family;
font-weight: $font-weight-regular;
color: $color-silver-dark;
position: absolute;
bottom: 0;
left: 0;
}
}
</style>

View File

@ -0,0 +1,48 @@
<template>
<div class="news-teasers">
<news-teaser
:key="teaser.id"
:teaser="teaser"
class="news-teasers__teaser teaser"
v-for="teaser in newsTeasers"/>
</div>
</template>
<script>
import NewsTeaser from '@/components/news/NewsTeaser';
import news from '@/mixins/news'
export default {
mixins: [news],
components: {
NewsTeaser
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_functions.scss";
@import "@/styles/_mixins.scss";
$image_height: 254px;
.news-teasers {
display: -ms-grid;
@supports (display: grid) {
display: grid;
}
margin-bottom: $large-spacing;
grid-gap: 40px;
@include desktop {
grid-column-gap: 40px;
grid-template-columns: repeat(auto-fit, minmax(320px, $news_width));
grid-auto-rows: minmax(400px, auto);
grid-template-rows: auto auto;
-ms-grid-columns: $news_width $news_width;
}
}
</style>

View File

@ -1,4 +1,5 @@
#import "./schoolClassParts.gql" #import "./schoolClassParts.gql"
#import "./moduleParts.gql"
fragment UserParts on UserNode { fragment UserParts on UserNode {
id id
pk pk
@ -19,6 +20,13 @@ fragment UserParts on UserNode {
selectedClass { selectedClass {
id id
} }
recentModules(orderBy: "-visited") {
edges {
node {
...ModuleParts
}
}
}
schoolClasses { schoolClasses {
edges { edges {
node { node {

View File

@ -1,9 +1,8 @@
#import "../fragments/moduleParts.gql" #import "../fragments/moduleParts.gql"
mutation UpdateLastModule($input: UpdateLastModuleInput!) { mutation UpdateLastModule($input: UpdateLastModuleInput!) {
updateLastModule(input: $input) { updateLastModule(input: $input) {
module { lastModule {
...ModuleParts ...ModuleParts
} }
errors
} }
} }

View File

@ -1,28 +0,0 @@
<template>
<div class="blank-layout">
<profile-sidebar/>
<navigation-sidebar/>
<router-view/>
</div>
</template>
<style lang="scss">
.blank-layout {
/*
For IE11
*/
display: flex;
}
</style>
<script>
import ProfileSidebar from '@/components/profile/ProfileSidebar';
import NavigationSidebar from '@/components/book-navigation/NavigationSidebar';
export default {
components: {
ProfileSidebar,
NavigationSidebar
},
}
</script>

View File

@ -15,6 +15,20 @@ export default {
} }
}, },
computed: {
topicRoute() {
if (this.me.lastTopic && this.me.lastTopic.slug) {
return {
name: 'topic',
params: {
topicSlug: this.me.lastTopic.slug
}
}
}
return '/book/topic/berufliche-grundbildung'
}
},
apollo: { apollo: {
me: { me: {
query: ME_QUERY, query: ME_QUERY,

18
client/src/mixins/news.js Normal file
View File

@ -0,0 +1,18 @@
import NEWS_TEASER_QUERY from '@/graphql/gql/newsTeasersQuery.gql';
export default {
data() {
return {
newsTeasers: []
};
},
apollo: {
newsTeasers: {
query: NEWS_TEASER_QUERY,
update(data) {
return this.$getRidOfEdges(data).newsTeasers;
},
client: 'publicClient',
}
}
}

View File

@ -1,13 +1,13 @@
<template> <template>
<div class="news"> <div class="news">
<h1 class="news__heading">News</h1> <h1 class="news__heading">News</h1>
<NewsTeasers /> <news-teasers />
</div> </div>
</template> </template>
<script> <script>
import NewsTeasers from '@/components/NewsTeasers'; import NewsTeasers from '@/components/news/NewsTeasers';
export default { export default {
components: {NewsTeasers}, components: {NewsTeasers},

View File

@ -0,0 +1,259 @@
<template>
<div class="start-page">
<header-bar class="start-page__header"/>
<div class="start-page__sections start-sections">
<section-block
:link-text="moduleText"
:route="moduleRoute"
class="start-sections__section"
title="Inhalte"
subtitle="Neues Wissen erwerben"
data-cy="current-module-link"
>
<contents-illustration/>
</section-block>
<section-block
class="start-sections__section"
title="Räume"
subtitle="Beiträge mit der Klasse teilen"
data-cy="rooms-link"
link-text="Alle Räume anzeigen"
route="/rooms"
>
<rooms-illustration/>
</section-block>
<section-block
class="start-sections__section"
title="Portfolio"
subtitle="Projekt dokumentieren und reflektieren"
link-text="Portfolio anzeigen"
route="/portfolio"
>
<portfolio-illustration/>
</section-block>
</div>
<div class="start-page__news news">
<h2 class="news__title">News</h2>
<news-teaser
date="27. Mai 2020"
title="Lockdown"
url="https://myskillbox-abu-news.webflow.io/lockdown"/>
<news-teaser
date="11. März 2020"
title="Brexit"
url="https://myskillbox-abu-news.webflow.io/brexit"/>
<news-teaser
date="20. Dezember 2019"
title="Blockchain"
url="https://myskillbox-abu-news.webflow.io/blockchain"/>
<!--<news-teaser date="31. Oktober 2018" title="Sommerzeit - Festivalzeit"-->
<!--url="https://abunews.webflow.io/"></news-teaser>-->
<div class="news__more">Mehr...</div>
</div>
</div>
</template>
<script>
import SectionBlock from '@/components/SectionBlock.vue';
import NewsTeaser from '@/components/news/NewsTeaserOld.vue';
import HeaderBar from '@/components/HeaderBar';
import ContentsIllustration from '@/components/illustrations/ContentsIllustration';
import PortfolioIllustration from '@/components/illustrations/PortfolioIllustration';
import RoomsIllustration from '@/components/illustrations/RoomsIllustration';
import MobileHeader from '@/components/MobileHeader';
import meMixin from '@/mixins/me';
export default {
mixins: [meMixin],
components: {
MobileHeader,
HeaderBar,
SectionBlock,
NewsTeaser,
ContentsIllustration,
PortfolioIllustration,
RoomsIllustration
},
computed: {
moduleRoute() {
if (this.me.lastModule && this.me.lastModule.slug) {
return `/module/${this.me.lastModule.slug}`;
}
return '/book/topic/berufliche-grundbildung';
},
moduleText() {
if (this.me.lastModule && this.me.lastModule.slug) {
return 'Aktuelles Modul anzeigen';
}
return 'Alle Inhalte anzeigen'
}
},
}
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
.start-page {
display: flex;
flex-direction: column;
justify-content: space-between;
@supports (display: grid) {
display: grid;
justify-content: stretch;
}
grid-template-rows: auto 1fr auto;
min-height: 100vh;
width: 100vw;
box-sizing: border-box;
&__header {
}
&__title {
color: $color-brand;
text-align: center;
border-bottom: 1px solid $color-silver-light;
padding-bottom: 30px;
margin-left: 30px;
margin-right: 30px;
margin-bottom: 60px;
}
&__my {
color: $color-white;
/*font-size: 2rem;*/
font-weight: 800;
-webkit-text-stroke: 1px $color-brand;
/*text-shadow: -1px -1px 0 $color-brand,
1px -1px 0 $color-brand,
-1px 1px 0 $color-brand,
1px 1px 0 $color-brand;*/
}
}
.start-sections {
margin: 0 30px;
@include desktop {
padding-left: 120px;
padding-right: 120px;
}
display: -ms-grid;
-ms-grid-column-align: center;
-ms-grid-columns: 1fr 1fr 1fr;
margin-bottom: 90px;
@supports (display: grid) {
display: grid;
}
@include desktop {
grid-template-columns: 1fr 1fr 1fr;
}
grid-row-gap: 15px;
grid-column-gap: 50px;
justify-items: start;
align-items: center;
/*
* For IE10+
*/
& > :nth-child(1) {
-ms-grid-row: 1;
-ms-grid-column: 1;
}
& > :nth-child(2) {
-ms-grid-row: 1;
-ms-grid-column: 2;
}
& > :nth-child(3) {
-ms-grid-row: 1;
-ms-grid-column: 3;
}
}
.news {
background-color: $color-charcoal-dark;
color: $color-white;
padding-top: $large-spacing;
padding-bottom: $large-spacing;
display: flex;
justify-content: space-around;
-ms-grid-columns: 1fr 1fr 1fr;
@supports (display: grid) {
display: grid;
grid-template-columns: 1fr;
}
grid-row-gap: $large-spacing;
@include desktop {
grid-template-columns: repeat(5, 1fr);
}
/*
* For IE10+
*/
& > :nth-child(1) {
-ms-grid-row: 1;
-ms-grid-column: 1;
}
& > :nth-child(2) {
-ms-grid-row: 1;
-ms-grid-column: 2;
}
& > :nth-child(3) {
-ms-grid-row: 1;
-ms-grid-column: 3;
}
& > :nth-child(4) {
-ms-grid-row: 1;
-ms-grid-column: 4;
}
@include desktop {
/*padding-left: 120px;*/
/*padding-right: 120px;*/
}
&__title {
font-family: $sans-serif-font-family;
font-weight: $font-weight-bold;
text-transform: uppercase;
font-size: 2.1875rem;
padding-bottom: 24px;
text-align: center;
color: inherit;
margin-bottom: 0;
}
&__more {
color: $color-white;
font-family: $sans-serif-font-family;
line-height: $default-line-height;
padding-left: $medium-spacing;
font-weight: $font-weight-bold;
text-align: center;
@include desktop {
text-align: left;
border-left: 1px solid $color-silver-light;
}
}
}
</style>

View File

@ -1,63 +1,55 @@
<template> <template>
<div class="start-page"> <div class="start-page">
<header-bar class="start-page__header"/> <div class="start-page__content">
<div
class="start-page__modules start-sections"
data-cy="start-modules-list">
<h2 class="start-page__heading">Letzte Module</h2>
<h3
class="start-page__no-modules"
data-cy="no-modules-yet"
v-if="!me.recentModules.length">Sie haben sich noch kein Modul angeschaut. Legen Sie jetzt los!</h3>
<div class="start-page__modules-list">
<module-teaser
:key="index"
:meta-title="module.metaTitle"
:title="module.title"
:hero-image="module.heroImage"
:slug="module.slug"
data-cy="start-module-teaser"
v-for="(module, index) in me.recentModules"/>
<div class="start-page__sections start-sections"> </div>
<router-link
:to="topicRoute"
tag="div"
class="button">Alle Module anzeigen
</router-link>
</div>
<div class="start-page__news news">
<h2 class="start-page__heading">News</h2>
<div class="news__list">
<news-teaser
:teaser="teaser"
:key="teaser.id"
v-for="teaser in teasers"/>
</div>
<section-block <router-link
:link-text="moduleText" :to="{name: 'news'}"
:route="moduleRoute" class="button">Alle News anzeigen
class="start-sections__section" </router-link>
title="Inhalte" </div>
subtitle="Neues Wissen erwerben"
data-cy="current-module-link"
>
<contents-illustration/>
</section-block>
<section-block
class="start-sections__section"
title="Räume"
subtitle="Beiträge mit der Klasse teilen"
data-cy="rooms-link"
link-text="Alle Räume anzeigen"
route="/rooms"
>
<rooms-illustration/>
</section-block>
<section-block
class="start-sections__section"
title="Portfolio"
subtitle="Projekt dokumentieren und reflektieren"
link-text="Portfolio anzeigen"
route="/portfolio"
>
<portfolio-illustration/>
</section-block>
</div>
<div class="start-page__news news">
<h2 class="news__title">News</h2>
<news-teaser
date="27. Mai 2020"
title="Lockdown"
url="https://myskillbox-abu-news.webflow.io/lockdown"/>
<news-teaser
date="11. März 2020"
title="Brexit"
url="https://myskillbox-abu-news.webflow.io/brexit"/>
<news-teaser
date="20. Dezember 2019"
title="Blockchain"
url="https://myskillbox-abu-news.webflow.io/blockchain"/>
<!--<news-teaser date="31. Oktober 2018" title="Sommerzeit - Festivalzeit"-->
<!--url="https://abunews.webflow.io/"></news-teaser>-->
<div class="news__more">Mehr...</div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import SectionBlock from '@/components/SectionBlock.vue'; import SectionBlock from '@/components/SectionBlock.vue';
import NewsTeaser from '@/components/NewsTeaser.vue'; import NewsTeaser from '@/components/news/NewsTeaser.vue';
import NewsTeasers from '@/components/news/NewsTeasers.vue';
import HeaderBar from '@/components/HeaderBar'; import HeaderBar from '@/components/HeaderBar';
import ModuleTeaser from '@/components/modules/ModuleTeaser';
import ContentsIllustration from '@/components/illustrations/ContentsIllustration'; import ContentsIllustration from '@/components/illustrations/ContentsIllustration';
import PortfolioIllustration from '@/components/illustrations/PortfolioIllustration'; import PortfolioIllustration from '@/components/illustrations/PortfolioIllustration';
@ -65,19 +57,23 @@
import MobileHeader from '@/components/MobileHeader'; import MobileHeader from '@/components/MobileHeader';
import meMixin from '@/mixins/me'; import meQuery from '@/mixins/me';
import news from '@/mixins/news';
export default { export default {
mixins: [meMixin], mixins: [meQuery, news],
components: { components: {
MobileHeader, MobileHeader,
HeaderBar, HeaderBar,
SectionBlock, SectionBlock,
NewsTeaser, NewsTeaser,
NewsTeasers,
ContentsIllustration, ContentsIllustration,
PortfolioIllustration, PortfolioIllustration,
RoomsIllustration RoomsIllustration,
ModuleTeaser
}, },
computed: { computed: {
@ -92,6 +88,9 @@
return 'Aktuelles Modul anzeigen'; return 'Aktuelles Modul anzeigen';
} }
return 'Alle Inhalte anzeigen' return 'Alle Inhalte anzeigen'
},
teasers() {
return this.newsTeasers.slice(0, 2);
} }
}, },
@ -109,17 +108,13 @@
justify-content: space-between; justify-content: space-between;
@supports (display: grid) { @supports (display: grid) {
display: grid; display: grid;
justify-content: stretch; justify-content: center;
} }
grid-template-rows: auto 1fr auto; grid-template-rows: auto 1fr auto;
min-height: 100vh; min-height: 100vh;
width: 100vw; width: 100vw;
box-sizing: border-box; box-sizing: border-box;
&__header {
}
&__title { &__title {
color: $color-brand; color: $color-brand;
text-align: center; text-align: center;
@ -130,77 +125,89 @@
margin-bottom: 60px; margin-bottom: 60px;
} }
&__my { &__heading {
color: $color-white; width: 100%;
/*font-size: 2rem;*/ @include meta-title;
font-weight: 800;
-webkit-text-stroke: 1px $color-brand;
/*text-shadow: -1px -1px 0 $color-brand,
1px -1px 0 $color-brand,
-1px 1px 0 $color-brand,
1px 1px 0 $color-brand;*/
}
}
.start-sections {
margin: 0 30px;
@include desktop {
padding-left: 120px;
padding-right: 120px;
}
display: -ms-grid;
-ms-grid-column-align: center;
-ms-grid-columns: 1fr 1fr 1fr;
margin-bottom: 90px;
@supports (display: grid) {
display: grid;
}
@include desktop {
grid-template-columns: 1fr 1fr 1fr;
} }
grid-row-gap: 15px; &__content {
grid-column-gap: 50px; padding-top: 2*$large-spacing;
justify-items: start; box-sizing: border-box;
align-items: center; max-width: 100%;
@include desktop {
/* max-width: 1200px;
* For IE10+ margin: 0 auto;
*/ }
& > :nth-child(1) {
-ms-grid-row: 1;
-ms-grid-column: 1;
} }
& > :nth-child(2) { &__no-modules {
-ms-grid-row: 1; @include heading-3;
-ms-grid-column: 2; margin-bottom: 0;
} }
& > :nth-child(3) { &__modules {
-ms-grid-row: 1; margin-bottom: $large-spacing;
-ms-grid-column: 3;
} }
&__modules-list {
display: -ms-grid;
-ms-grid-column-align: center;
-ms-grid-columns: 1fr 1fr 1fr;
width: 100%;
margin-bottom: $large-spacing;
@supports (display: grid) {
display: grid;
}
@include desktop {
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: max-content;
}
grid-row-gap: 15px;
grid-column-gap: 50px;
justify-items: start;
align-items: center;
/*
* For IE10+
*/
& > :nth-child(1) {
-ms-grid-row: 1;
-ms-grid-column: 1;
}
& > :nth-child(2) {
-ms-grid-row: 1;
-ms-grid-column: 2;
}
& > :nth-child(3) {
-ms-grid-row: 1;
-ms-grid-column: 3;
}
}
} }
.news { .news {
background-color: $color-charcoal-dark;
color: $color-white;
padding-top: $large-spacing;
padding-bottom: $large-spacing; padding-bottom: $large-spacing;
display: flex;
justify-content: space-around;
-ms-grid-columns: 1fr 1fr 1fr;
@supports (display: grid) {
display: grid;
grid-template-columns: 1fr;
}
grid-row-gap: $large-spacing; &__list {
display: flex;
@include desktop { grid-row-gap: $large-spacing;
grid-template-columns: repeat(5, 1fr); grid-column-gap: $large-spacing;
margin-bottom: $large-spacing;
@supports (display: grid) {
display: grid;
grid-template-columns: 1fr;
}
@include desktop {
grid-template-columns: repeat(2, 1fr);
}
} }
/* /*

View File

@ -5,7 +5,9 @@
</div> </div>
<div class="topic__content"> <div class="topic__content">
<h1 class="topic__title">{{ topic.title }}</h1> <h1
data-cy="topic-title"
class="topic__title">{{ topic.title }}</h1>
<p class="topic__teaser"> <p class="topic__teaser">
{{ topic.teaser }} {{ topic.teaser }}
</p> </p>

View File

@ -46,8 +46,7 @@ const routes = [
{ {
path: '/', path: '/',
name: 'home', name: 'home',
component: start, component: start
meta: {layout: 'blank'}
}, },
{ {
path: '/login', path: '/login',

View File

@ -77,3 +77,4 @@ $default-heading-line-height: 1.2;
$popover-default-bottom: -110px; $popover-default-bottom: -110px;
$footer-width: 800px; $footer-width: 800px;
$news_width: 550px;

View File

@ -0,0 +1,26 @@
# Generated by Django 2.2.12 on 2020-06-23 09:52
import datetime
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('books', '0021_auto_20200520_0954'),
]
operations = [
migrations.CreateModel(
name='RecentModule',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('visited', models.DateTimeField(default=datetime.datetime.now)),
('module', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='books.Module')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,4 +1,5 @@
import logging import logging
from datetime import datetime
from django.db import models from django.db import models
from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, ObjectList from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, ObjectList
@ -58,3 +59,12 @@ class Module(StrictHierarchyPage):
def get_child_ids(self): def get_child_ids(self):
return self.get_children().values_list('id', flat=True) return self.get_children().values_list('id', flat=True)
class RecentModule(models.Model):
module = models.ForeignKey(Module, on_delete=models.CASCADE, related_name='recent_modules')
user = models.ForeignKey('users.User', on_delete=models.CASCADE)
visited = models.DateTimeField(default=datetime.now)
class Meta:
get_latest_by = 'visited'

View File

@ -1,8 +1,10 @@
from datetime import datetime
import graphene import graphene
from graphene import relay from graphene import relay
from api.utils import get_errors, get_object from api.utils import get_errors, get_object
from books.models import Module, Topic from books.models import Module, Topic, RecentModule
from books.schema.queries import ModuleNode, TopicNode from books.schema.queries import ModuleNode, TopicNode
@ -47,30 +49,30 @@ class UpdateLastModule(relay.ClientIDMutation):
# todo: use slug here too # todo: use slug here too
id = graphene.ID() id = graphene.ID()
module = graphene.Field(ModuleNode) last_module = graphene.Field(ModuleNode)
errors = graphene.List(graphene.String)
@classmethod @classmethod
def mutate_and_get_payload(cls, root, info, **args): def mutate_and_get_payload(cls, root, info, **args):
user = info.context.user
id = args.get('id')
module = get_object(Module, id)
if not module:
raise Module.DoesNotExist
try: try:
user = info.context.user same_module = RecentModule.objects.get(user=user, module=module)
id = args.get('id') same_module.visited = datetime.now()
same_module.save()
return cls(last_module=same_module.module)
except RecentModule.DoesNotExist:
recent_modules = RecentModule.objects.filter(user=user)
while recent_modules.all().count() >= 3:
recent_modules.earliest().delete()
module = get_object(Module, id) last_module = RecentModule.objects.create(user=user, module=module)
if not module:
raise Module.DoesNotExist
user.last_module = module return cls(last_module=last_module.module)
user.save()
return cls(module=module)
except Module.DoesNotExist:
return cls(errors=['Module not found'])
except Exception as e:
errors = ['Error: {}'.format(e)]
return cls(errors=errors)
class UpdateLastTopic(relay.ClientIDMutation): class UpdateLastTopic(relay.ClientIDMutation):
@ -93,4 +95,3 @@ class UpdateLastTopic(relay.ClientIDMutation):
user.save() user.save()
return cls(topic=topic) return cls(topic=topic)

View File

@ -13,7 +13,7 @@ from notes.schema import ContentBlockBookmarkNode, ChapterBookmarkNode, ModuleBo
from rooms.models import ModuleRoomSlug from rooms.models import ModuleRoomSlug
from surveys.models import Answer from surveys.models import Answer
from surveys.schema import AnswerNode from surveys.schema import AnswerNode
from ..models import Book, Topic, Module, Chapter, ContentBlock from ..models import Book, Topic, Module, Chapter, ContentBlock, RecentModule
def process_module_room_slug_block(content): def process_module_room_slug_block(content):
@ -187,6 +187,12 @@ class ModuleNode(DjangoObjectType):
.prefetch_related('objectives__objective_progress') .prefetch_related('objectives__objective_progress')
class RecentModuleNode(DjangoObjectType):
class Meta:
model = RecentModule
interfaces = (relay.Node,)
class TopicNode(DjangoObjectType): class TopicNode(DjangoObjectType):
pk = graphene.Int() pk = graphene.Int()
modules = DjangoFilterConnectionField(ModuleNode) modules = DjangoFilterConnectionField(ModuleNode)

View File

@ -0,0 +1,126 @@
from django.test import TestCase, RequestFactory
from graphene.test import Client
from graphql_relay import to_global_id
from api.schema import schema
from api.utils import get_object
from books.models import ContentBlock, Chapter
from books.factories import ModuleFactory, BookFactory
from core.factories import UserFactory
from users.services import create_users
class RecentModulesTest(TestCase):
def setUp(self):
create_users()
_, topic, self.module1, chapter, _ = BookFactory.create_default_structure()
self.modules = []
for i in range(1, 5):
module = ModuleFactory.create(parent=topic,
title=f"Another module {i}",
meta_title=f"Modul {i}",
teaser="Whatever",
intro="<p>Hello</p>")
self.modules.append(module)
user = UserFactory(username='aschi')
request = RequestFactory().get('/')
request.user = user
self.client = Client(schema=schema, context_value=request)
def _update_last_module(self, module):
mutation = """
mutation UpdateLastModule($input: UpdateLastModuleInput!) {
updateLastModule(input: $input) {
lastModule {
id
}
}
}
"""
result = self.client.execute(mutation, variables={
'input': {
'id': to_global_id('ModuleNode', module.id)
}
})
self.assertIsNone(result.get('errors'))
def _get_recent_modules(self):
recent_modules_query = """
query RecentModulesQuery {
me {
recentModules(orderBy: "-visited") {
edges {
node {
id
title
}
}
}
}
}
"""
return list(map(lambda x: x['node'], self.client.execute(recent_modules_query).get('data').get('me').get('recentModules').get('edges')))
def test_no_recent_modules(self):
edges = self._get_recent_modules()
self.assertEqual(len(edges), 0)
def test_update_last_module(self):
self._update_last_module(self.module1)
def test_recent_modules(self):
module1, module2, module3, module4 = self.modules
self._update_last_module(module1)
recent_modules = self._get_recent_modules()
self.assertEqual(len(recent_modules), 1)
self.assertEqual(recent_modules[0].get('title'), module1.title)
# same module twice
self._update_last_module(module1)
recent_modules = self._get_recent_modules()
self.assertEqual(len(recent_modules), 1)
self._update_last_module(module2)
recent_modules = self._get_recent_modules()
self.assertEqual(len(recent_modules), 2)
self.assertEqual(recent_modules[0].get('title'), module2.title)
self.assertEqual(recent_modules[1].get('title'), module1.title)
# same module as before again
self._update_last_module(module1)
recent_modules = self._get_recent_modules()
self.assertEqual(len(recent_modules), 2)
self.assertEqual(recent_modules[0].get('title'), module1.title)
self.assertEqual(recent_modules[1].get('title'), module2.title)
self._update_last_module(module3)
recent_modules = self._get_recent_modules()
self.assertEqual(len(recent_modules), 3)
self.assertEqual(recent_modules[0].get('title'), module3.title)
self.assertEqual(recent_modules[1].get('title'), module1.title)
self.assertEqual(recent_modules[2].get('title'), module2.title)
self._update_last_module(module4)
recent_modules = self._get_recent_modules()
self.assertEqual(len(recent_modules), 3)
self.assertEqual(recent_modules[0].get('title'), module4.title)
self.assertEqual(recent_modules[1].get('title'), module3.title)
self.assertEqual(recent_modules[2].get('title'), module1.title)
# same again
self._update_last_module(module4)
recent_modules = self._get_recent_modules()
self.assertEqual(len(recent_modules), 3)
self.assertEqual(recent_modules[0].get('title'), module4.title)
self.assertEqual(recent_modules[1].get('title'), module3.title)
self.assertEqual(recent_modules[2].get('title'), module1.title)

View File

@ -1,15 +1,6 @@
from .settings import * from .settings import *
MIGRATION_MODULES = {}
class DisableMigrations(object):
def __contains__(self, item):
return True
def __getitem__(self, item):
return None
MIGRATION_MODULES = DisableMigrations()
# Email Settings # Email Settings
SENDGRID_API_KEY = "" SENDGRID_API_KEY = ""

View File

@ -3,7 +3,7 @@
set -e set -e
cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
python -m coverage run manage.py test $1 python -m coverage run manage.py test --settings=core.settings_test $1
coverage_python=`coverage report -m | tail -n1 | awk '{print $4}'` coverage_python=`coverage report -m | tail -n1 | awk '{print $4}'`
commit=`git rev-parse HEAD` commit=`git rev-parse HEAD`

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.12 on 2020-06-23 09:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('books', '0022_recentmodule'),
('users', '0019_userdata'),
]
operations = [
migrations.AddField(
model_name='user',
name='recent_modules',
field=models.ManyToManyField(related_name='_user_recent_modules_+', through='books.RecentModule', to='books.Module'),
),
]

View File

@ -17,6 +17,7 @@ DEFAULT_SCHOOL_ID = 1
class User(AbstractUser): class User(AbstractUser):
last_module = models.ForeignKey('books.Module', related_name='+', on_delete=models.SET_NULL, null=True) last_module = models.ForeignKey('books.Module', related_name='+', on_delete=models.SET_NULL, null=True)
recent_modules = models.ManyToManyField('books.Module', related_name='+', through='books.RecentModule')
last_topic = models.ForeignKey('books.Topic', related_name='+', on_delete=models.SET_NULL, null=True) last_topic = models.ForeignKey('books.Topic', related_name='+', on_delete=models.SET_NULL, null=True)
avatar_url = models.CharField(max_length=254, blank=True, default='') avatar_url = models.CharField(max_length=254, blank=True, default='')
email = models.EmailField(_('email address'), unique=True) email = models.EmailField(_('email address'), unique=True)
@ -97,7 +98,6 @@ class User(AbstractUser):
user_settings.selected_class = school_class user_settings.selected_class = school_class
user_settings.save() user_settings.save()
@property @property
def full_name(self): def full_name(self):
return self.get_full_name() return self.get_full_name()
@ -245,7 +245,7 @@ class UserData(models.Model):
class License(models.Model): class License(models.Model):
for_role = models.ForeignKey(Role, blank=False, null=True, on_delete=models.CASCADE) for_role = models.ForeignKey(Role, blank=False, null=True, on_delete=models.CASCADE)
licensee = models.ForeignKey(User, blank=False, null=True, on_delete=models.CASCADE) licensee = models.ForeignKey(User, blank=False, null=True, on_delete=models.CASCADE)
expire_date = models.DateField(blank=False, null=True,) expire_date = models.DateField(blank=False, null=True, )
order_id = models.IntegerField(blank=False, null=False, default=-1) order_id = models.IntegerField(blank=False, null=False, default=-1)
raw = models.TextField(default='') raw = models.TextField(default='')
@ -255,8 +255,9 @@ class License(models.Model):
return self.for_role.key == RoleManager.TEACHER_KEY return self.for_role.key == RoleManager.TEACHER_KEY
def is_valid(self): def is_valid(self):
return HepClient.is_product_active(datetime(self.expire_date.year, self.expire_date.month, self.expire_date.day), return HepClient.is_product_active(
self.for_role.key) datetime(self.expire_date.year, self.expire_date.month, self.expire_date.day),
self.for_role.key)
def __str__(self): def __str__(self):
return f'License for role: {self.for_role}' return f'License for role: {self.for_role}'
@ -266,4 +267,3 @@ class SchoolClassMember(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE) school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE)
active = models.BooleanField(default=True) active = models.BooleanField(default=True)

View File

@ -2,6 +2,7 @@ from datetime import datetime
import graphene import graphene
from django.db.models import Q from django.db.models import Q
from django_filters import FilterSet, OrderingFilter
from graphene import relay, ObjectType from graphene import relay, ObjectType
from graphene_django import DjangoObjectType from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField from graphene_django.filter import DjangoFilterConnectionField
@ -10,7 +11,7 @@ from graphql_relay import to_global_id
from basicknowledge.models import BasicKnowledge from basicknowledge.models import BasicKnowledge
from basicknowledge.queries import InstrumentNode from basicknowledge.queries import InstrumentNode
from books.models import Module from books.models import Module, RecentModule
from books.schema.queries import ModuleNode from books.schema.queries import ModuleNode
from users.models import User, SchoolClass, SchoolClassMember from users.models import User, SchoolClass, SchoolClassMember
@ -39,6 +40,18 @@ class SchoolClassNode(DjangoObjectType):
return self.code return self.code
class RecentModuleFilter(FilterSet):
class Meta:
model = Module
fields = ('recent_modules',)
order_by = OrderingFilter(
fields=(
('recent_modules__visited', 'visited'),
)
)
class UserNode(DjangoObjectType): class UserNode(DjangoObjectType):
pk = graphene.Int() pk = graphene.Int()
permissions = graphene.List(graphene.String) permissions = graphene.List(graphene.String)
@ -46,6 +59,7 @@ class UserNode(DjangoObjectType):
expiry_date = graphene.String() expiry_date = graphene.String()
is_teacher = graphene.Boolean() is_teacher = graphene.Boolean()
old_classes = DjangoFilterConnectionField(SchoolClassNode) old_classes = DjangoFilterConnectionField(SchoolClassNode)
recent_modules = DjangoFilterConnectionField(ModuleNode, filterset_class=RecentModuleFilter)
class Meta: class Meta:
model = User model = User
@ -82,6 +96,10 @@ class UserNode(DjangoObjectType):
def resolve_old_classes(self, info): def resolve_old_classes(self, info):
return SchoolClass.objects.filter(schoolclassmember__active=False, schoolclassmember__user=self) return SchoolClass.objects.filter(schoolclassmember__active=False, schoolclassmember__user=self)
def resolve_recent_modules(self, info, **kwargs):
# see https://docs.graphene-python.org/projects/django/en/latest/filtering/
return RecentModuleFilter(kwargs).qs.filter(recent_modules__user=self)
class ClassMemberNode(ObjectType): class ClassMemberNode(ObjectType):
""" """