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
variables:
POSTGRES_HOST_AUTH_METHOD: trust
caches:
npm: $HOME/.npm
cypress: $HOME/.cache/Cypress
aliases:
- &lint
@ -39,6 +43,7 @@ aliases:
caches:
- pip
- node
- npm
artifacts:
- client/cypress/**/*.png
- client/cypress/**/*.mp4
@ -47,8 +52,8 @@ aliases:
script:
- echo "This pipeline rules!"
- *setup-tests
- npm install --prefix client
# - npm run "install:cypress" --prefix client
- npm ci --prefix client
- npm run "install:cypress" --prefix client
- psql -U $DATABASE_USER -h $DATABASE_HOST -c "create database $DATABASE_NAME"
- python server/manage.py dummy_data
- 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,
"username": "rahel.cueni",
"email": "rahel.cueni@skillbox.example",
"expiryDate": "3596153600",
"firstName": "Rahel",
"lastName": "Cueni",
"avatarUrl": "",
@ -17,6 +18,11 @@
"id": "U2Nob29sQ2xhc3NOb2RlOjI=",
"__typename": "SchoolClassNode"
},
"lastTopic": {
"id": "VG9waWNOb2RlOjU=",
"slug": "geld-und-kauf",
"__typename": "TopicNode"
},
"schoolClasses": {
"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';
cy.visit('/beta-login');
cy.login(username, password, true);
cy.get('body').contains('Neues Wissen erwerben');
cy.assertStartPage();
});
it('user sees error message if username is omitted', () => {
@ -35,7 +35,6 @@ describe('The Login Page', () => {
});
it('logs out then logs in again', () => {
const user = 'rahel.cueni';
const pw = 'test'

View File

@ -1,12 +1,12 @@
describe('Bookmarks', () => {
beforeEach(() => {
// 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.startGraphQLCapture();
cy.login('rahel.cueni', 'test', true);
cy.get('body').contains('Neues Wissen erwerben');
cy.assertStartPage();
});
it('should bookmark content block', () => {
@ -35,5 +35,4 @@ describe('Bookmarks', () => {
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');
@ -20,23 +20,21 @@ describe('Email Verifcation', () => {
}
});
cy.login('rahel.cueni', 'test', true)
cy.get('[data-cy="rooms-link"]').contains('Alle Räume anzeigen');
cy.assertStartPage();
cy.visit('/license-activation');
cy.redeemCoupon('12345asfd');
cy.get('body').contains('Neues Wissen erwerben');
cy.assertStartPage();
});
it('displays error if input is missing', () => {
cy.viewport('macbook-15');
cy.login('rahel.cueni', 'test', true)
cy.get('[data-cy="rooms-link"]').contains('Alle Räume anzeigen');
cy.assertStartPage();
cy.visit('/license-activation');
cy.redeemCoupon('');
cy.get('[data-cy="coupon-local-errors"]').contains('Coupon ist ein Pflichtfeld.');
});
it('displays error if coupon input is wrong', () => {
@ -48,12 +46,11 @@ describe('Email Verifcation', () => {
}
});
cy.login('rahel.cueni', 'test', true)
cy.get('[data-cy="rooms-link"]').contains('Alle Räume anzeigen');
cy.assertStartPage();
cy.visit('/license-activation');
cy.redeemCoupon('12345asfd');
cy.get('[data-cy="coupon-remote-errors"]').contains('Der angegebene Coupon-Code ist ungültig.');
});
it('displays error if an error occures', () => {
@ -61,16 +58,14 @@ describe('Email Verifcation', () => {
cy.mockGraphql({
schema: schema,
operations: {
Coupon: new GraphQLError("unknown_error")
Coupon: new GraphQLError('unknown_error')
}
});
cy.login('rahel.cueni', 'test', true)
cy.get('[data-cy="rooms-link"]').contains('Alle Räume anzeigen');
cy.assertStartPage();
cy.visit('/license-activation');
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.');
});
});

View File

@ -1,12 +1,65 @@
const schema = require('../fixtures/schema.json');
const assignments = require('../fixtures/assignments.json');
const lohnModule = require('../fixtures/module.json');
const geldModule = require('../fixtures/module-geld.json');
const mePayload = require('../fixtures/me.join-class.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', () => {
before(() => {
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({
schema: schema,
// endpoint: '/api/graphql'
@ -14,16 +67,7 @@ describe('Current Module', () => {
MeQuery: variables => {
return {
me: {
'lastModule': {
// 'id': 'TW9kdWxlTm9kZToxNw==',
'slug': 'lohn-und-budget',
'__typename': 'ModuleNode'
},
'lastTopic': {
'id': 'VG9waWNOb2RlOjU=',
'slug': 'geld-und-kauf',
'__typename': 'TopicNode'
},
...me,
'__typename': 'UserNode',
'permissions': []
}
@ -33,28 +77,22 @@ describe('Current Module', () => {
assignments
},
ModulesQuery: variables => {
let module;
if (variables.slug === 'lohn-und-budget') {
module = lohnModule;
} else {
module = geldModule
}
return {
module
module: fullModules[variables.slug]
}
},
TopicsQuery: topics,
Topic: topic,
UpdateLastTopic: {
'updateLastTopic': {
'topic': topic.topic,
'__typename': 'UpdateLastTopicPayload'
}
},
UpdateLastModule: variables => {
let module;
if (variables.input.id === 'TW9kdWxlTm9kZToxNw==') {
module = lohnModule
} else {
module = geldModule
}
return {
updateLastModule: {
module,
errors: null,
lastModule: moduleTeasers[variables.input.id],
__typename: 'UpdateLastModulePayload'
}
}
@ -67,21 +105,50 @@ describe('Current Module', () => {
cy.viewport('macbook-15');
cy.apolloLogin('nico.zickgraf', 'test');
cy.visit('/book/topic/geld-und-kauf');
cy.contains('Modul 1').click();
cy.visit('/');
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.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.goToModule('Geld und Kauf', 'Modul 2');
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.get('[data-cy="current-module-link"]').click();
cy.get('[data-cy=module-title]').should('contain', 'Geld')
cy.goToModule('Geld und Kauf', 'Modul 1');
cy.get('[data-cy=module-title]').should('contain', 'Lohn und Budget');
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 '@iam4x/cypress-graphql-mock';
Cypress.Commands.add('apolloLogin', (username, password) => {
const payload = {
'operationName': 'BetaLogin',
@ -47,12 +46,10 @@ Cypress.Commands.add('apolloLogin', (username, password) => {
url: '/api/graphql-public/',
body: payload
});
});
// todo: replace with apollo call
Cypress.Commands.add("login", (username, password, visitLogin = false) => {
Cypress.Commands.add('login', (username, password, visitLogin = false) => {
if (visitLogin) {
cy.visit('/beta-login');
}
@ -67,7 +64,7 @@ Cypress.Commands.add("login", (username, password, visitLogin = false) => {
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=logout]').click();
});
@ -122,7 +119,6 @@ Cypress.Commands.add('enterPassword', (password) => {
});
Cypress.Commands.add('register', (prefix, firstname, lastname, street, city, postcode, password, passwordConfirmation, acceptTerms) => {
let selection = prefix === 1 ? 'Herr' : 'Frau';
cy.get('[data-cy="prefix-selection"]').select(selection);
@ -153,7 +149,7 @@ Cypress.Commands.add('register', (prefix, firstname, lastname, street, city, pos
if (acceptTerms) {
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="passwordConfirmation-input"]').type(passwordConfirmation);
@ -167,3 +163,6 @@ Cypress.Commands.add('redeemCoupon', coupon => {
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": {
"version": "1.2.4",
"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",
"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": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",
@ -3395,9 +3481,9 @@
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
},
"arch": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz",
"integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/arch/-/arch-2.1.2.tgz",
"integrity": "sha512-NTBIIbAfkJeIletyABbVtdPgeKfDafR+1mZV/AyyfC1UkVkp9iUjV+wwmqtUgphHYajbI86jejBJp5e+jkGTiQ==",
"dev": true
},
"are-we-there-yet": {
@ -7032,41 +7118,42 @@
"integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA="
},
"cypress": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-4.2.0.tgz",
"integrity": "sha512-8LdreL91S/QiTCLYLNbIjLL8Ht4fJmu/4HGLxUI20Tc7JSfqEfCmXELrRfuPT0kjosJwJJZacdSji9XSRkPKUw==",
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-4.9.0.tgz",
"integrity": "sha512-qGxT5E0j21FPryzhb0OBjCdhoR/n1jXtumpFFSBPYWsaZZhNaBvc3XlBUDEZKkkXPsqUFYiyhWdHN/zo0t5FcA==",
"dev": true,
"requires": {
"@cypress/listr-verbose-renderer": "0.4.1",
"@cypress/request": "2.88.5",
"@cypress/xvfb": "1.2.4",
"@types/sinonjs__fake-timers": "6.0.1",
"@types/sizzle": "2.3.2",
"arch": "2.1.1",
"arch": "2.1.2",
"bluebird": "3.7.2",
"cachedir": "2.3.0",
"chalk": "2.4.2",
"check-more-types": "2.24.0",
"cli-table3": "0.5.1",
"commander": "4.1.0",
"commander": "4.1.1",
"common-tags": "1.8.0",
"debug": "4.1.1",
"eventemitter2": "4.1.2",
"eventemitter2": "6.4.2",
"execa": "1.0.0",
"executable": "4.1.1",
"extract-zip": "1.6.7",
"extract-zip": "1.7.0",
"fs-extra": "8.1.0",
"getos": "3.1.4",
"getos": "3.2.1",
"is-ci": "2.0.0",
"is-installed-globally": "0.1.0",
"is-installed-globally": "0.3.2",
"lazy-ass": "1.6.0",
"listr": "0.14.3",
"lodash": "4.17.15",
"log-symbols": "3.0.0",
"minimist": "1.2.2",
"moment": "2.24.0",
"minimist": "1.2.5",
"moment": "2.26.0",
"ospath": "1.2.2",
"pretty-bytes": "5.3.0",
"ramda": "0.26.1",
"request": "github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16",
"request-progress": "3.0.0",
"supports-color": "7.1.0",
"tmp": "0.1.0",
@ -7075,18 +7162,6 @@
"yauzl": "2.10.0"
},
"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": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@ -7116,9 +7191,9 @@
}
},
"commander": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.0.tgz",
"integrity": "sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true
},
"cross-spawn": {
@ -7158,12 +7233,6 @@
"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": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
@ -7187,22 +7256,6 @@
"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": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
@ -7219,9 +7272,15 @@
}
},
"minimist": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.2.tgz",
"integrity": "sha512-rIqbOrKb8GJmx/5bc2M0QchhUouMXSpd1RTclXsB41JdL+VtnojfaJR+h7F9k18/4kHUsBFgk80Uk+q569vjPA==",
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"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
},
"ms": {
@ -7230,12 +7289,6 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"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": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@ -7246,33 +7299,6 @@
"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": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@ -7307,16 +7333,6 @@
"requires": {
"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": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-4.1.2.tgz",
"integrity": "sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU=",
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.2.tgz",
"integrity": "sha512-r/Pwupa5RIzxIHbEKCkNXqpEQIIT4uQDxmP4G/Lug/NokVUWj0joz/WzWl3OxRpC5kDrH/WdiUJoR+IrwvXJEw==",
"dev": true
},
"eventemitter3": {
@ -8705,15 +8721,15 @@
}
},
"extract-zip": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz",
"integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=",
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz",
"integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==",
"dev": true,
"requires": {
"concat-stream": "1.6.2",
"debug": "2.6.9",
"mkdirp": "0.5.1",
"yauzl": "2.4.1"
"concat-stream": "^1.6.2",
"debug": "^2.6.9",
"mkdirp": "^0.5.4",
"yauzl": "^2.10.0"
},
"dependencies": {
"debug": {
@ -8725,13 +8741,19 @@
"ms": "2.0.0"
}
},
"yauzl": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
"integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=",
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"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,
"requires": {
"fd-slicer": "~1.0.1"
"minimist": "^1.2.5"
}
}
}
@ -8779,9 +8801,9 @@
}
},
"fd-slicer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
"integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
"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"
@ -9072,9 +9094,9 @@
},
"dependencies": {
"graceful-fs": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"dev": true
},
"jsonfile": {
@ -9685,12 +9707,12 @@
"integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
},
"getos": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/getos/-/getos-3.1.4.tgz",
"integrity": "sha512-UORPzguEB/7UG5hqiZai8f0vQ7hzynMQyJLxStoQ8dPGAcmgsfXOPA4iE/fGtweHYkK+z4zc9V0g+CIFRf5HYw==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz",
"integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==",
"dev": true,
"requires": {
"async": "^3.1.0"
"async": "^3.2.0"
},
"dependencies": {
"async": {
@ -9783,12 +9805,12 @@
}
},
"global-dirs": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
"integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz",
"integrity": "sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==",
"dev": true,
"requires": {
"ini": "^1.3.4"
"ini": "^1.3.5"
}
},
"globals": {
@ -10705,13 +10727,21 @@
}
},
"is-installed-globally": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz",
"integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=",
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz",
"integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==",
"dev": true,
"requires": {
"global-dirs": "^0.1.0",
"is-path-inside": "^1.0.0"
"global-dirs": "^2.0.1",
"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": {
@ -17915,9 +17945,9 @@
}
},
"rxjs": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz",
"integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==",
"version": "6.5.5",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz",
"integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
@ -20729,17 +20759,6 @@
"requires": {
"buffer-crc32": "~0.2.3",
"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": {

View File

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

View File

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

View File

@ -2,6 +2,7 @@
<header class="header-bar">
<a
class="header-bar__sidebar-link"
data-cy="open-sidebar-link"
@click="openSidebar('navigation')">
<hamburger class="header-bar__sidebar-icon"/>
</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;
background-color: $color-white;
border: 1px solid $color-silver;
z-index: 2;
&__icon {
width: 50px;

View File

@ -90,20 +90,6 @@
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: {
isActive(linkName) {
return linkName === 'book' && this.$route.path.indexOf('module') > -1;

View File

@ -52,6 +52,7 @@
import {withoutOwnerFirst} from '@/helpers/sorting';
import BookmarkActions from '@/components/notes/BookmarkActions';
import meMixin from '@/mixins/me';
export default {
@ -65,20 +66,15 @@
default: false
}
},
mixins: [meMixin],
components: {
BookmarkActions,
ObjectiveGroups,
Chapter
},
data() {
return {
me: {
permissions: []
}
}
},
computed: {
languageCommunicationObjectiveGroups() {
return this.module.objectiveGroups ? this.module.objectiveGroups
@ -114,11 +110,26 @@
id: moduleId
}
},
update(store, {data: {updateLastModule: {module}}}) {
if (module) {
update(store, {data: {updateLastModule: {lastModule}}}) {
if (lastModule) {
const data = store.readQuery({query: ME_QUERY});
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});
}
}
@ -199,12 +210,6 @@
this.$store.dispatch('editNote', this.module.bookmark.note);
},
},
apollo: {
me: {
query: ME_QUERY,
}
},
}
</script>

View File

@ -1,8 +1,9 @@
<template>
<router-link
:to="moduleLink"
:class="['module-teaser', {'module-teaser--small': !teaser}]"
tag="div"
class="module-teaser">
>
<div
:style="{backgroundImage: 'url('+heroImage+')'}"
class="module-teaser__image"/>
@ -46,6 +47,10 @@
box-sizing: border-box;
cursor: pointer;
&--small {
height: 300px;
}
&__image {
width: 100%;
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 "./moduleParts.gql"
fragment UserParts on UserNode {
id
pk
@ -19,6 +20,13 @@ fragment UserParts on UserNode {
selectedClass {
id
}
recentModules(orderBy: "-visited") {
edges {
node {
...ModuleParts
}
}
}
schoolClasses {
edges {
node {

View File

@ -1,9 +1,8 @@
#import "../fragments/moduleParts.gql"
mutation UpdateLastModule($input: UpdateLastModuleInput!) {
updateLastModule(input: $input) {
module {
lastModule {
...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: {
me: {
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>
<div class="news">
<h1 class="news__heading">News</h1>
<NewsTeasers />
<news-teasers />
</div>
</template>
<script>
import NewsTeasers from '@/components/NewsTeasers';
import NewsTeasers from '@/components/news/NewsTeasers';
export default {
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>
<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
: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>
<router-link
:to="{name: 'news'}"
class="button">Alle News anzeigen
</router-link>
</div>
</div>
</div>
</template>
<script>
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 ModuleTeaser from '@/components/modules/ModuleTeaser';
import ContentsIllustration from '@/components/illustrations/ContentsIllustration';
import PortfolioIllustration from '@/components/illustrations/PortfolioIllustration';
@ -65,19 +57,23 @@
import MobileHeader from '@/components/MobileHeader';
import meMixin from '@/mixins/me';
import meQuery from '@/mixins/me';
import news from '@/mixins/news';
export default {
mixins: [meMixin],
mixins: [meQuery, news],
components: {
MobileHeader,
HeaderBar,
SectionBlock,
NewsTeaser,
NewsTeasers,
ContentsIllustration,
PortfolioIllustration,
RoomsIllustration
RoomsIllustration,
ModuleTeaser
},
computed: {
@ -92,6 +88,9 @@
return 'Aktuelles Modul anzeigen';
}
return 'Alle Inhalte anzeigen'
},
teasers() {
return this.newsTeasers.slice(0, 2);
}
},
@ -109,17 +108,13 @@
justify-content: space-between;
@supports (display: grid) {
display: grid;
justify-content: stretch;
justify-content: center;
}
grid-template-rows: auto 1fr auto;
min-height: 100vh;
width: 100vw;
box-sizing: border-box;
&__header {
}
&__title {
color: $color-brand;
text-align: center;
@ -130,77 +125,89 @@
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;
&__heading {
width: 100%;
@include meta-title;
}
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;
&__content {
padding-top: 2*$large-spacing;
box-sizing: border-box;
max-width: 100%;
@include desktop {
max-width: 1200px;
margin: 0 auto;
}
}
& > :nth-child(2) {
-ms-grid-row: 1;
-ms-grid-column: 2;
&__no-modules {
@include heading-3;
margin-bottom: 0;
}
& > :nth-child(3) {
-ms-grid-row: 1;
-ms-grid-column: 3;
&__modules {
margin-bottom: $large-spacing;
}
&__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 {
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;
&__list {
display: flex;
@include desktop {
grid-template-columns: repeat(5, 1fr);
grid-row-gap: $large-spacing;
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 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">
{{ topic.teaser }}
</p>

View File

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

View File

@ -77,3 +77,4 @@ $default-heading-line-height: 1.2;
$popover-default-bottom: -110px;
$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
from datetime import datetime
from django.db import models
from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, ObjectList
@ -58,3 +59,12 @@ class Module(StrictHierarchyPage):
def get_child_ids(self):
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
from graphene import relay
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
@ -47,30 +49,30 @@ class UpdateLastModule(relay.ClientIDMutation):
# todo: use slug here too
id = graphene.ID()
module = graphene.Field(ModuleNode)
errors = graphene.List(graphene.String)
last_module = graphene.Field(ModuleNode)
@classmethod
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:
user = info.context.user
id = args.get('id')
same_module = RecentModule.objects.get(user=user, module=module)
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)
if not module:
raise Module.DoesNotExist
last_module = RecentModule.objects.create(user=user, module=module)
user.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)
return cls(last_module=last_module.module)
class UpdateLastTopic(relay.ClientIDMutation):
@ -93,4 +95,3 @@ class UpdateLastTopic(relay.ClientIDMutation):
user.save()
return cls(topic=topic)

View File

@ -13,7 +13,7 @@ from notes.schema import ContentBlockBookmarkNode, ChapterBookmarkNode, ModuleBo
from rooms.models import ModuleRoomSlug
from surveys.models import Answer
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):
@ -187,6 +187,12 @@ class ModuleNode(DjangoObjectType):
.prefetch_related('objectives__objective_progress')
class RecentModuleNode(DjangoObjectType):
class Meta:
model = RecentModule
interfaces = (relay.Node,)
class TopicNode(DjangoObjectType):
pk = graphene.Int()
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 *
class DisableMigrations(object):
def __contains__(self, item):
return True
def __getitem__(self, item):
return None
MIGRATION_MODULES = DisableMigrations()
MIGRATION_MODULES = {}
# Email Settings
SENDGRID_API_KEY = ""

View File

@ -3,7 +3,7 @@
set -e
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}'`
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):
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)
avatar_url = models.CharField(max_length=254, blank=True, default='')
email = models.EmailField(_('email address'), unique=True)
@ -97,7 +98,6 @@ class User(AbstractUser):
user_settings.selected_class = school_class
user_settings.save()
@property
def full_name(self):
return self.get_full_name()
@ -245,7 +245,7 @@ class UserData(models.Model):
class License(models.Model):
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)
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)
raw = models.TextField(default='')
@ -255,8 +255,9 @@ class License(models.Model):
return self.for_role.key == RoleManager.TEACHER_KEY
def is_valid(self):
return HepClient.is_product_active(datetime(self.expire_date.year, self.expire_date.month, self.expire_date.day),
self.for_role.key)
return HepClient.is_product_active(
datetime(self.expire_date.year, self.expire_date.month, self.expire_date.day),
self.for_role.key)
def __str__(self):
return f'License for role: {self.for_role}'
@ -266,4 +267,3 @@ class SchoolClassMember(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE)
active = models.BooleanField(default=True)

View File

@ -2,6 +2,7 @@ from datetime import datetime
import graphene
from django.db.models import Q
from django_filters import FilterSet, OrderingFilter
from graphene import relay, ObjectType
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
@ -10,7 +11,7 @@ from graphql_relay import to_global_id
from basicknowledge.models import BasicKnowledge
from basicknowledge.queries import InstrumentNode
from books.models import Module
from books.models import Module, RecentModule
from books.schema.queries import ModuleNode
from users.models import User, SchoolClass, SchoolClassMember
@ -39,6 +40,18 @@ class SchoolClassNode(DjangoObjectType):
return self.code
class RecentModuleFilter(FilterSet):
class Meta:
model = Module
fields = ('recent_modules',)
order_by = OrderingFilter(
fields=(
('recent_modules__visited', 'visited'),
)
)
class UserNode(DjangoObjectType):
pk = graphene.Int()
permissions = graphene.List(graphene.String)
@ -46,6 +59,7 @@ class UserNode(DjangoObjectType):
expiry_date = graphene.String()
is_teacher = graphene.Boolean()
old_classes = DjangoFilterConnectionField(SchoolClassNode)
recent_modules = DjangoFilterConnectionField(ModuleNode, filterset_class=RecentModuleFilter)
class Meta:
model = User
@ -82,6 +96,10 @@ class UserNode(DjangoObjectType):
def resolve_old_classes(self, info):
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):
"""