Merged in feature/apply-visibility (pull request #80)
Feature/apply visibility Approved-by: Christian Cueni
This commit is contained in:
commit
77db03eea7
|
|
@ -221,3 +221,12 @@ To generate a new schema, use the management command
|
|||
```
|
||||
python manage.py export_schema_for_cypress
|
||||
```
|
||||
|
||||
|
||||
## GraphQL
|
||||
|
||||
### Generate GraphQL SDL Document
|
||||
|
||||
```
|
||||
python manage.py export_schema_graphql
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,5 +5,6 @@
|
|||
"reporterOptions": {
|
||||
"mochaFile": "cypress/test-reports/cypress-results-[hash].xml",
|
||||
"toConsole": true
|
||||
}
|
||||
},
|
||||
"$schema": "https://on.cypress.io/cypress.schema.json"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
"extends": [
|
||||
"plugin:cypress/recommended"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// import * as schema from '../fixtures/schema.json';
|
||||
import {getModules, getMe} from '../support/helpers';
|
||||
|
||||
const mocks = {
|
||||
UUID: () => 'Whatever',
|
||||
GenericStreamFieldType: () => [],
|
||||
};
|
||||
|
||||
const operations = {
|
||||
MeQuery() {
|
||||
return getMe({
|
||||
schoolClasses: ['FLID2018a', 'Andere Klasse'],
|
||||
teacher: true,
|
||||
});
|
||||
},
|
||||
ModulesQuery: getModules,
|
||||
UpdateSettings: {
|
||||
updateSettings: {
|
||||
success: true,
|
||||
},
|
||||
},
|
||||
MySchoolClassQuery: {
|
||||
me: {},
|
||||
},
|
||||
UpdateLastModule: {
|
||||
updateLastModule: {
|
||||
success: true,
|
||||
},
|
||||
},
|
||||
SyncModuleVisibility: {
|
||||
syncModuleVisibility: {
|
||||
success: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('Apply module visibility', () => {
|
||||
beforeEach(() => {
|
||||
cy.server();
|
||||
cy.task('getSchema').then(schema => {
|
||||
cy.mockGraphql({
|
||||
schema,
|
||||
// endpoint: '/api/graphql'
|
||||
mocks,
|
||||
operations,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('needs to be implemented', () => {
|
||||
// Cypress.config({
|
||||
// baseUrl: 'http://localhost:8080',
|
||||
// });
|
||||
cy.viewport('macbook-15');
|
||||
|
||||
// login as teacher
|
||||
// cy.fakeLogin('nico.zickgraf', 'test');
|
||||
cy.apolloLogin('nico.zickgraf', 'test');
|
||||
// cy.wait('@gqlBetaLogin');
|
||||
// go to module
|
||||
cy.visit('/module/lohn-und-budget');
|
||||
cy.selectClass('Andere Klasse');
|
||||
// click on settings
|
||||
cy.getByDataCy('module-settings-button').click();
|
||||
// click on select button
|
||||
cy.getByDataCy('select-school-class-button').click();
|
||||
// select schoolclass
|
||||
cy.getByDataCy('school-class-visibility-dropdown').select('FLID2018a');
|
||||
// save changes
|
||||
cy.getByDataCy('save-visibility-button').click();
|
||||
|
||||
cy.getByDataCy('module-title').should('exist');
|
||||
});
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { GraphQLError } from 'graphql';
|
||||
|
||||
const schema = require('../fixtures/schema.json');
|
||||
const schema = require('../../fixtures/schema.json');
|
||||
|
||||
describe('Email Verifcation', () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -19,7 +19,7 @@ describe('Email Verifcation', () => {
|
|||
},
|
||||
}
|
||||
});
|
||||
cy.apolloLogin('rahel.cueni', 'test')
|
||||
cy.apolloLogin('rahel.cueni', 'test');
|
||||
|
||||
cy.visit('/license-activation');
|
||||
cy.redeemCoupon('12345asfd');
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
const schema = require('../fixtures/schema_public.json');
|
||||
const schema = require('../../fixtures/schema_public.json');
|
||||
const isEmailAvailableUrl = '**/rest/deutsch/V1/customers/isEmailAvailable';
|
||||
const checkPasswordUrl = '**/rest/deutsch/V1/integration/customer/token';
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ describe('Login', () => {
|
|||
message: 'success',
|
||||
success: true
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
@ -69,5 +69,5 @@ describe('Login', () => {
|
|||
|
||||
cy.checkEmailAvailable('feuzaebi.ch');
|
||||
cy.get('[data-cy="email-local-errors"]').contains('Bitte geben Sie eine gülitge E-Mail an');
|
||||
})
|
||||
});
|
||||
});
|
||||
|
|
@ -11,7 +11,18 @@
|
|||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
import {readFileSync} from 'fs';
|
||||
import {resolve} from 'path';
|
||||
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
}
|
||||
on('task', {
|
||||
getSchema() {
|
||||
return readFileSync(
|
||||
resolve(__dirname, '../../../schema.graphql'),
|
||||
'utf8'
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ Cypress.Commands.add('redeemCoupon', coupon => {
|
|||
cy.get('[data-cy="coupon-input"]').type(coupon);
|
||||
}
|
||||
cy.get('[data-cy="coupon-button"]').click();
|
||||
})
|
||||
});
|
||||
|
||||
Cypress.Commands.add('assertStartPage', (onboarding) => {
|
||||
if (onboarding) {
|
||||
|
|
@ -174,3 +174,18 @@ Cypress.Commands.add('assertStartPage', (onboarding) => {
|
|||
Cypress.Commands.add('skipOnboarding', (onboarding) => {
|
||||
cy.get('[data-cy=onboarding-skip-link]').click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('getByDataCy', (selector) => {
|
||||
return cy.get(`[data-cy=${selector}]`);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('selectClass', (schoolClass) => {
|
||||
cy.getByDataCy('user-widget-avatar').click();
|
||||
cy.getByDataCy('class-selection').click();
|
||||
cy.getByDataCy('class-selection-entry').contains(schoolClass).click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('fakeLogin', () => {
|
||||
cy.log('Logging in (fake)');
|
||||
cy.setCookie('loginStatus', 'true');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,353 @@
|
|||
const getSchoolClassNode = (id, schoolClassName) => ({
|
||||
'id': btoa(`SchoolClassNode:${id}`),
|
||||
'name': schoolClassName,
|
||||
'__typename': 'SchoolClassNode',
|
||||
});
|
||||
|
||||
export const getMe = ({schoolClasses, teacher}) => {
|
||||
let schoolClassNodes;
|
||||
if (schoolClasses) {
|
||||
schoolClassNodes = schoolClasses.map((schoolClass, i) => getSchoolClassNode(i, schoolClass));
|
||||
} else {
|
||||
schoolClassNodes = [getSchoolClassNode(1, 'FLID2018a')];
|
||||
}
|
||||
|
||||
return {
|
||||
'me': {
|
||||
'id': 'VXNlck5vZGU6NQ==',
|
||||
'pk': 5,
|
||||
'username': 'rahel.cueni',
|
||||
'email': 'rahel.cueni@skillbox.example',
|
||||
'expiryDate': '3596153600',
|
||||
'firstName': 'Rahel',
|
||||
'lastName': 'Cueni',
|
||||
'avatarUrl': '',
|
||||
'isTeacher': false,
|
||||
'lastModule': {
|
||||
'id': 'TW9kdWxlTm9kZToxNw==',
|
||||
'slug': 'lohn-und-budget',
|
||||
'__typename': 'ModuleNode',
|
||||
},
|
||||
'selectedClass': {
|
||||
'id': 'U2Nob29sQ2xhc3NOb2RlOjI=',
|
||||
'__typename': 'SchoolClassNode',
|
||||
},
|
||||
'lastTopic': {
|
||||
'id': 'VG9waWNOb2RlOjU=',
|
||||
'slug': 'geld-und-kauf',
|
||||
'__typename': 'TopicNode',
|
||||
},
|
||||
'schoolClasses': {
|
||||
'edges': schoolClassNodes.map(scn => ({
|
||||
node: scn,
|
||||
'__typename': 'SchoolClassNodeEdge',
|
||||
})),
|
||||
'__typename': 'SchoolClassNodeConnection',
|
||||
},
|
||||
'__typename': 'UserNode',
|
||||
'onboardingVisited': true,
|
||||
'permissions': teacher ? ['users.can_manage_school_class_content'] : [],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getAssignments = () => {
|
||||
return {
|
||||
'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': '\ud83d\ude42\ud83d\ude10\ud83e\udd2c\ud83d\udc4d\ud83e\udd22\ud83e\udd22\ud83e\udd22\ud83e\udd22\ud83d\ude2e\ud83e\udd17',
|
||||
'teacher': {
|
||||
'firstName': 'Nico',
|
||||
'lastName': 'Zickgraf',
|
||||
'__typename': 'UserNode',
|
||||
},
|
||||
'__typename': 'SubmissionFeedbackNode',
|
||||
},
|
||||
'__typename': 'StudentSubmissionNode',
|
||||
},
|
||||
'__typename': 'AssignmentNode',
|
||||
},
|
||||
'__typename': 'AssignmentNodeEdge',
|
||||
},
|
||||
],
|
||||
'__typename': 'AssignmentNodeConnection',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getModules = () => {
|
||||
return {
|
||||
'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',
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Intellisense for custom Commands: https://github.com/cypress-io/cypress-example-todomvc#cypress-intellisense
|
||||
declare namespace Cypress {
|
||||
interface Chainable<Subject> {
|
||||
/**
|
||||
* Login via API call to the GraphQL endpoint, without calling the frontend. Faster than the other login.
|
||||
* @param username
|
||||
* @param password
|
||||
* @example
|
||||
* cy.apolloLogin('nico.zickgraf', 'test')
|
||||
*/
|
||||
apolloLogin(username: string, password: string): Chainable<any>
|
||||
|
||||
/**
|
||||
* Selects an element based on the `data-cy=xxx` attribute
|
||||
* @param selector - The value of the data-cy attribute to select
|
||||
* @example
|
||||
* cy.getByDataCy('my-new-button')
|
||||
*/
|
||||
getByDataCy(selector: string): Chainable<any>
|
||||
|
||||
selectClass(schoolClass: string): void
|
||||
|
||||
login(username:string, password:string, visitLogin?: boolean): void
|
||||
fakeLogin(username:string, password:string): void
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"resolveJsonModule": true,
|
||||
"types": [
|
||||
"cypress"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.*"
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -44,11 +44,6 @@
|
|||
"eslint-config-standard": "^10.2.1",
|
||||
"eslint-friendly-formatter": "^3.0.0",
|
||||
"eslint-loader": "^1.7.1",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
"eslint-plugin-node": "^5.2.0",
|
||||
"eslint-plugin-promise": "^3.4.0",
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
"eslint-plugin-vue": "^4.0.0",
|
||||
"extract-text-webpack-plugin": "^3.0.0",
|
||||
"file-loader": "^1.1.4",
|
||||
"friendly-errors-webpack-plugin": "^1.6.1",
|
||||
|
|
@ -112,11 +107,19 @@
|
|||
"babel-jest": "^24.8.0",
|
||||
"canvas": "^2.5.0",
|
||||
"cypress": "^6.2.1",
|
||||
"eslint-plugin-cypress": "^2.11.2",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
"eslint-plugin-node": "^5.2.0",
|
||||
"eslint-plugin-promise": "^3.4.0",
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
"eslint-plugin-vue": "^4.0.0",
|
||||
"graphql-config": "^3.2.0",
|
||||
"jest": "^24.8.0",
|
||||
"jest-serializer-vue": "^2.0.2",
|
||||
"jest-transform-graphql": "^2.1.0",
|
||||
"jest-transform-stub": "^2.0.0",
|
||||
"jest-watch-typeahead": "^0.3.1",
|
||||
"typescript": "^4.2.3",
|
||||
"vue-jest": "^3.0.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import BaseInput from '@/components/inputs/BaseInput';
|
||||
import BaseInput from '@/components/ui/BaseInput';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
import AssignmentForm from '@/components/content-forms/AssignmentForm';
|
||||
import TextForm from '@/components/content-forms/TextForm';
|
||||
import TrashIcon from '@/components/icons/TrashIcon';
|
||||
import Checkbox from '@/components/Checkbox';
|
||||
import Checkbox from '@/components/ui/Checkbox';
|
||||
|
||||
import {meQuery} from '@/graphql/queries';
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Checkbox from '@/components/Checkbox';
|
||||
import Checkbox from '@/components/ui/Checkbox';
|
||||
|
||||
export default {
|
||||
|
||||
|
|
|
|||
|
|
@ -45,13 +45,15 @@
|
|||
class="module-navigation__toggle-menu"
|
||||
v-if="canManageContent"
|
||||
>
|
||||
<a
|
||||
class="module-navigation__actions"
|
||||
data-cy="module-snapshots-button">Snapshots</a>
|
||||
|
||||
<router-link
|
||||
:to="{name: 'module-settings'}"
|
||||
class="module-navigation__actions"
|
||||
data-cy="module-settings-button">Einstellungen</router-link>
|
||||
<toggle-editing v-if="onModulePage"/>
|
||||
<toggle-solutions-for-module
|
||||
:slug="module.slug"
|
||||
:enabled="module.solutionsEnabled"
|
||||
class="module-navigation__solution-toggle"
|
||||
data-cy="toggle-enable-solutions"
|
||||
v-if="onModulePage && module.id"/>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
|
@ -159,8 +161,7 @@
|
|||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
@import "~styles/helpers";
|
||||
|
||||
@mixin module-navigation-typography {
|
||||
font-family: $sans-serif-font-family;
|
||||
|
|
@ -220,5 +221,10 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
@include regular-text;
|
||||
margin-right: $medium-spacing;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -9,15 +9,6 @@
|
|||
|
||||
export default {
|
||||
mixins: [me],
|
||||
|
||||
computed: {
|
||||
currentClassName() {
|
||||
let currentClass = this.me.schoolClasses.find(schoolClass => {
|
||||
return schoolClass.id === this.me.selectedClass.id;
|
||||
});
|
||||
return currentClass ? currentClass.name : this.me.schoolClasses.length ? this.me.schoolClasses[0].name : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +1,29 @@
|
|||
<template>
|
||||
<checkbox
|
||||
<toggle
|
||||
:checked="checked"
|
||||
class="toggle-editing"
|
||||
label="Modul anpassen"
|
||||
@input="toggle"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Checkbox from '@/components/Checkbox';
|
||||
import {mapState, mapActions} from 'vuex';
|
||||
import Toggle from '@/components/ui/Toggle';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Checkbox,
|
||||
Toggle
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState({
|
||||
checked: 'editModule',
|
||||
})
|
||||
}),
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions({
|
||||
toggle: 'editModule'
|
||||
})
|
||||
}
|
||||
toggle: 'editModule',
|
||||
}),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Checkbox from '@/components/Checkbox';
|
||||
import Checkbox from '@/components/ui/Checkbox';
|
||||
import UPDATE_SOLUTION_VISIBILITY_MUTATION from '@/graphql/gql/mutations/updateSolutionVisibility.gql';
|
||||
import MODULE_FRAGMENT from '@/graphql/gql/fragments/moduleParts.gql';
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import BaseInput from '@/components/inputs/BaseInput';
|
||||
import BaseInput from '@/components/ui/BaseInput';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<template>
|
||||
<label
|
||||
:for="id"
|
||||
class="toggle">
|
||||
<input
|
||||
:id="id"
|
||||
:checked="checked"
|
||||
class="toggle__input"
|
||||
type="checkbox"
|
||||
@change.prevent="$emit('input', $event.target.checked)">
|
||||
<span class="toggle__toggle-wrapper">
|
||||
<span class="toggle__toggle"/>
|
||||
</span>
|
||||
<span class="toggle__label">{{ label }}</span>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
checked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
id: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.id = this._uid;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~styles/helpers';
|
||||
|
||||
.toggle {
|
||||
@include default-box-shadow;
|
||||
border-radius: $round-border-radius;
|
||||
border: 1px solid $color-silver;
|
||||
padding: $small-spacing $medium-spacing;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
|
||||
&__toggle-wrapper {
|
||||
background-color: $color-silver;
|
||||
width: 42px;
|
||||
height: 24px;
|
||||
border-radius: $round-border-radius;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: 0 6px;
|
||||
margin-right: $small-spacing;
|
||||
}
|
||||
|
||||
&__input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__input:checked + &__toggle-wrapper {
|
||||
background-color: $color-brand;
|
||||
}
|
||||
|
||||
&__input:checked + &__toggle-wrapper &__toggle {
|
||||
margin-left: 14px;
|
||||
}
|
||||
|
||||
&__toggle {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: $round-border-radius;
|
||||
background-color: $color-white;
|
||||
margin-left: 0;
|
||||
transition: margin-left 0.5s;
|
||||
}
|
||||
|
||||
&__label {
|
||||
@include regular-text;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import {InMemoryCache, defaultDataIdFromObject} from 'apollo-cache-inmemory/lib/index';
|
||||
import {InMemoryCache, defaultDataIdFromObject} from 'apollo-cache-inmemory';
|
||||
import {createHttpLink} from 'apollo-link-http';
|
||||
import {onError} from 'apollo-link-error';
|
||||
import {ApolloClient} from 'apollo-client';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
mutation SyncModuleVisibility($input: SyncModuleVisibilityInput!) {
|
||||
syncModuleVisibility(input: $input) {
|
||||
success
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import gql from 'graphql-tag';
|
||||
|
||||
export const typeDefs = gql`
|
||||
type SidebarInput {
|
||||
input SidebarInput {
|
||||
navigation: Boolean
|
||||
profile: Boolean
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ export default {
|
|||
return {
|
||||
me: {
|
||||
selectedClass: {
|
||||
id: ''
|
||||
id: '',
|
||||
},
|
||||
permissions: [],
|
||||
schoolClasses: [],
|
||||
isTeacher: false
|
||||
isTeacher: false,
|
||||
},
|
||||
showPopover: false
|
||||
showPopover: false,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -21,8 +21,8 @@ export default {
|
|||
return {
|
||||
name: 'topic',
|
||||
params: {
|
||||
topicSlug: this.me.lastTopic.slug
|
||||
}
|
||||
topicSlug: this.me.lastTopic.slug,
|
||||
},
|
||||
};
|
||||
}
|
||||
return '/book/topic/berufliche-grundbildung';
|
||||
|
|
@ -33,15 +33,23 @@ export default {
|
|||
canManageContent() {
|
||||
return this.me.permissions.includes('users.can_manage_school_class_content');
|
||||
},
|
||||
currentClassName() {
|
||||
let currentClass = this.me.schoolClasses.find(schoolClass => {
|
||||
return schoolClass.id === this.me.selectedClass.id;
|
||||
});
|
||||
return currentClass
|
||||
? currentClass.name
|
||||
: (this.me.schoolClasses.length ? this.me.schoolClasses[0].name : '');
|
||||
},
|
||||
},
|
||||
|
||||
apollo: {
|
||||
me: {
|
||||
query: ME_QUERY,
|
||||
update(data) {
|
||||
return this.$getRidOfEdges(data).me;
|
||||
update({me}) {
|
||||
return this.$getRidOfEdges(me);
|
||||
},
|
||||
fetchPolicy: 'cache-first'
|
||||
fetchPolicy: 'cache-first',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="module-page">
|
||||
<module-navigation/>
|
||||
<module-navigation v-if="showNavigation" />
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -11,6 +11,12 @@
|
|||
export default {
|
||||
components: {
|
||||
ModuleNavigation
|
||||
},
|
||||
|
||||
computed: {
|
||||
showNavigation() {
|
||||
return !this.$route.meta.hideNavigation;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div class="module-settings">
|
||||
<h1 class="module-settings__page-title">Einstellungen</h1>
|
||||
<section class="module-settings__section">
|
||||
<h2 class="module-settings__heading">Lösungen</h2>
|
||||
<p class="module-settings__paragraph">Wollen Sie die Lösungen in diesem Modul für Lernende anzeigen?</p>
|
||||
<checkbox label="Lösungen anzeigen"/>
|
||||
</section>
|
||||
<section class="module-settings__section">
|
||||
<h2 class="module-settings__heading">Sichtbarkeit</h2>
|
||||
<p class="module-settings__paragraph">Haben Sie die Sichtbarkeit für eine andere Klasse bereits angepasst? Dann
|
||||
können Sie diese Anpassungen hier
|
||||
übernehmen</p>
|
||||
</section>
|
||||
<section class="module-settings__section">
|
||||
<router-link
|
||||
:to="{name: 'visibility'}"
|
||||
data-cy="select-school-class-button"
|
||||
class="button button--primary"
|
||||
>Klasse auswählen</router-link>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Checkbox from '@/components/ui/Checkbox';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Checkbox
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import 'styles/_helpers';
|
||||
|
||||
.module-settings {
|
||||
@include settings-page;
|
||||
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
<template>
|
||||
<div class="module-visibility">
|
||||
<h1 class="module-visibility__page-title">Sichtbarkeit</h1>
|
||||
<div class="module-visibility__section">
|
||||
<p class="module-visibility__paragraph">
|
||||
Wollen Sie die angepasste Sichtbarkeit (
|
||||
<eye-icon class="module-visibility__inline-icon"/>
|
||||
) von Inhalten einer anderen Klasse übernehmen?
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="module-visibility__form module-visibility__section">
|
||||
Von
|
||||
<select
|
||||
:value="selectedClassId"
|
||||
data-cy="school-class-visibility-dropdown"
|
||||
class="skillbox-input skillbox-dropdown module-visibility__dropdown"
|
||||
@change="select($event.target.value)">
|
||||
<option
|
||||
value=""
|
||||
selected>-
|
||||
</option>
|
||||
<option
|
||||
:key="schoolClass.id"
|
||||
:value="schoolClass.id"
|
||||
v-for="schoolClass in schoolClasses">{{ schoolClass.name }}
|
||||
</option>
|
||||
</select>
|
||||
für {{ currentClassName }} übernehmen.
|
||||
</div>
|
||||
<div class="module-visibility__section">
|
||||
<a
|
||||
class="button button--primary"
|
||||
data-cy="save-visibility-button"
|
||||
@click="sync">Anpassungen übernehmen</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EyeIcon from '@/components/icons/EyeIcon';
|
||||
|
||||
import me from '@/mixins/me';
|
||||
|
||||
import SYNC_VISIBILITY_MUTATION from '@/graphql/gql/mutations/syncModuleVisibility.gql';
|
||||
import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery';
|
||||
import {MODULE_PAGE} from '@/router/module.names';
|
||||
|
||||
export default {
|
||||
|
||||
mixins: [me],
|
||||
components: {
|
||||
EyeIcon,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectedClassId: '',
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
schoolClasses() {
|
||||
return this.me.schoolClasses.filter(schoolClass => schoolClass.id !== this.me.selectedClass.id);
|
||||
},
|
||||
slug() {
|
||||
return this.$route.params.slug;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
select(selectedClassId) {
|
||||
this.selectedClassId = selectedClassId;
|
||||
},
|
||||
sync() {
|
||||
if (this.selectedClassId) {
|
||||
const slug = this.slug;
|
||||
this.$apollo.mutate({
|
||||
mutation: SYNC_VISIBILITY_MUTATION,
|
||||
variables: {
|
||||
input: {
|
||||
module: slug,
|
||||
templateSchoolClass: this.selectedClassId,
|
||||
schoolClass: this.me.selectedClass.id,
|
||||
},
|
||||
},
|
||||
refetchQueries: [
|
||||
{
|
||||
query: MODULE_DETAILS_QUERY,
|
||||
variables: {
|
||||
slug,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
).then(() => {
|
||||
this.$router.push({
|
||||
name: MODULE_PAGE,
|
||||
params: {
|
||||
slug
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~styles/_helpers';
|
||||
|
||||
.module-visibility {
|
||||
@include settings-page;
|
||||
|
||||
margin: 0 auto;
|
||||
|
||||
&__inline-icon {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&__dropdown {
|
||||
width: 200px;
|
||||
margin: 0 $medium-spacing;
|
||||
}
|
||||
|
||||
&__form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@include regular-text;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -266,7 +266,7 @@
|
|||
|
||||
import {register} from '../hep-client/index';
|
||||
import HELLO_EMAIL from '@/graphql/gql/local/helloEmail.gql';
|
||||
import Checkbox from '@/components/Checkbox';
|
||||
import Checkbox from '@/components/ui/Checkbox';
|
||||
import LoadingButton from '@/components/LoadingButton';
|
||||
|
||||
import pageTitleMixin from '@/mixins/page-title';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
// import index from '@/pages/index'
|
||||
import topic from '@/pages/topic';
|
||||
import moduleBase from '@/pages/module-base';
|
||||
import module from '@/pages/module';
|
||||
import rooms from '@/pages/rooms';
|
||||
import room from '@/pages/room';
|
||||
import newRoom from '@/pages/newRoom';
|
||||
|
|
@ -10,7 +7,6 @@ import editRoom from '@/pages/editRoom';
|
|||
import article from '@/pages/article';
|
||||
import instrument from '@/pages/instrument';
|
||||
import instrumentOverview from '@/pages/instrumentOverview';
|
||||
import submissions from '@/pages/submissions';
|
||||
import p404 from '@/pages/p404';
|
||||
import start from '@/pages/start';
|
||||
import submission from '@/pages/studentSubmission';
|
||||
|
|
@ -21,7 +17,7 @@ import activity from '@/pages/activity';
|
|||
import Router from 'vue-router';
|
||||
import surveyPage from '@/pages/survey';
|
||||
import styleGuidePage from '@/pages/styleguide';
|
||||
import moduleRoom from '@/pages/moduleRoom';
|
||||
import moduleRoom from '@/pages/module/moduleRoom';
|
||||
import login from '@/pages/login';
|
||||
import betaLogin from '@/pages/beta-login';
|
||||
import hello from '@/pages/hello';
|
||||
|
|
@ -40,6 +36,8 @@ import onboardingStart from '@/pages/onboarding/start';
|
|||
import onboardingStep1 from '@/pages/onboarding/step1';
|
||||
import onboardingStep2 from '@/pages/onboarding/step2';
|
||||
import onboardingStep3 from '@/pages/onboarding/step3';
|
||||
|
||||
import moduleRoutes from './module.routes';
|
||||
import portfolioRoutes from './portfolio.routes';
|
||||
|
||||
import store from '@/store/index';
|
||||
|
|
@ -52,7 +50,7 @@ const routes = [
|
|||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: start
|
||||
component: start,
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
|
|
@ -60,8 +58,8 @@ const routes = [
|
|||
component: login,
|
||||
meta: {
|
||||
layout: 'public',
|
||||
public: true
|
||||
}
|
||||
public: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/hello',
|
||||
|
|
@ -69,8 +67,8 @@ const routes = [
|
|||
component: hello,
|
||||
meta: {
|
||||
layout: 'public',
|
||||
public: true
|
||||
}
|
||||
public: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/beta-login',
|
||||
|
|
@ -78,27 +76,10 @@ const routes = [
|
|||
component: betaLogin,
|
||||
meta: {
|
||||
layout: 'public',
|
||||
public: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/module/:slug',
|
||||
component: moduleBase,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'module',
|
||||
component: module,
|
||||
meta: {filter: true}
|
||||
},
|
||||
{
|
||||
path: 'submissions/:id',
|
||||
name: 'submissions',
|
||||
component: submissions,
|
||||
meta: {filter: true}
|
||||
}
|
||||
]
|
||||
public: true,
|
||||
},
|
||||
},
|
||||
...moduleRoutes,
|
||||
{path: '/rooms', name: 'rooms', component: rooms, meta: {filter: true}},
|
||||
{path: '/new-room/', name: 'new-room', component: newRoom},
|
||||
{path: '/edit-room/:id', name: 'edit-room', component: editRoom, props: true},
|
||||
|
|
@ -108,13 +89,13 @@ const routes = [
|
|||
name: 'moduleRoom',
|
||||
component: moduleRoom,
|
||||
props: true,
|
||||
meta: {layout: 'fullScreen'}
|
||||
meta: {layout: 'fullScreen'},
|
||||
},
|
||||
{path: '/article/:slug', name: 'article', component: article, meta: {layout: 'simple'}},
|
||||
{
|
||||
path: '/instruments/',
|
||||
name: 'instrument-overview',
|
||||
component: instrumentOverview
|
||||
component: instrumentOverview,
|
||||
},
|
||||
{path: '/instrument/:slug', name: 'instrument', component: instrument, meta: {layout: 'simple'}},
|
||||
{path: '/submission/:id', name: 'submission', component: submission, meta: {layout: 'simple'}},
|
||||
|
|
@ -132,11 +113,11 @@ const routes = [
|
|||
path: 'old-classes',
|
||||
name: 'old-classes',
|
||||
component: oldClasses,
|
||||
meta: {isProfile: true}
|
||||
meta: {isProfile: true},
|
||||
},
|
||||
{path: 'create-class', name: 'create-class', component: createClass, meta: {layout: 'simple'}},
|
||||
{path: 'show-code', name: 'show-code', component: showCode, meta: {layout: 'simple'}},
|
||||
]
|
||||
],
|
||||
},
|
||||
{path: 'join-class', name: 'join-class', component: joinClass, meta: {layout: 'public'}},
|
||||
{
|
||||
|
|
@ -144,7 +125,7 @@ const routes = [
|
|||
component: surveyPage,
|
||||
name: 'survey',
|
||||
props: true,
|
||||
meta: {layout: 'simple'}
|
||||
meta: {layout: 'simple'},
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
|
|
@ -153,7 +134,7 @@ const routes = [
|
|||
meta: {
|
||||
public: true,
|
||||
layout: 'public',
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/check-email',
|
||||
|
|
@ -161,8 +142,8 @@ const routes = [
|
|||
name: 'checkEmail',
|
||||
meta: {
|
||||
public: true,
|
||||
layout: 'public'
|
||||
}
|
||||
layout: 'public',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/verify-email',
|
||||
|
|
@ -170,16 +151,16 @@ const routes = [
|
|||
name: 'emailVerification',
|
||||
meta: {
|
||||
public: true,
|
||||
layout: 'public'
|
||||
}
|
||||
layout: 'public',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/license-activation',
|
||||
component: licenseActivation,
|
||||
name: 'licenseActivation',
|
||||
meta: {
|
||||
layout: 'public'
|
||||
}
|
||||
layout: 'public',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/forgot-password',
|
||||
|
|
@ -187,13 +168,13 @@ const routes = [
|
|||
name: 'forgotPassword',
|
||||
meta: {
|
||||
layout: 'public',
|
||||
public: true
|
||||
}
|
||||
public: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/news',
|
||||
component: news,
|
||||
name: 'news'
|
||||
name: 'news',
|
||||
},
|
||||
{
|
||||
path: '/onboarding',
|
||||
|
|
@ -205,7 +186,7 @@ const routes = [
|
|||
name: 'onboarding-start',
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
next: ONBOARDING_STEP_1
|
||||
next: ONBOARDING_STEP_1,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -215,7 +196,7 @@ const routes = [
|
|||
meta: {
|
||||
layout: 'blank',
|
||||
next: ONBOARDING_STEP_2,
|
||||
illustration: 'contents'
|
||||
illustration: 'contents',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -225,7 +206,7 @@ const routes = [
|
|||
meta: {
|
||||
layout: 'blank',
|
||||
next: ONBOARDING_STEP_3,
|
||||
illustration: 'rooms'
|
||||
illustration: 'rooms',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -235,19 +216,19 @@ const routes = [
|
|||
meta: {
|
||||
layout: 'blank',
|
||||
next: 'home',
|
||||
illustration: 'portfolio'
|
||||
illustration: 'portfolio',
|
||||
},
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
{path: '/styleguide', component: styleGuidePage},
|
||||
{
|
||||
path: '*',
|
||||
component: p404,
|
||||
meta: {
|
||||
layout: 'blank'
|
||||
}
|
||||
}
|
||||
layout: 'blank',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
Vue.use(Router);
|
||||
|
|
@ -260,7 +241,7 @@ const router = new Router({
|
|||
return savedPosition;
|
||||
}
|
||||
return {x: 0, y: 0};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
router.afterEach((to, from) => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
export const SUBMISSIONS_PAGE = 'submissions';
|
||||
export const MODULE_PAGE = 'module';
|
||||
export const MODULE_SETTINGS_PAGE = 'module-settings';
|
||||
export const VISIBILITY_PAGE = 'visibility';
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import moduleBase from '@/pages/module/module-base';
|
||||
import module from '@/pages/module/module';
|
||||
import submissions from '@/pages/submissions';
|
||||
import moduleVisibility from '@/pages/module/moduleVisibility';
|
||||
import {MODULE_PAGE, MODULE_SETTINGS_PAGE, SUBMISSIONS_PAGE, VISIBILITY_PAGE} from '@/router/module.names';
|
||||
import settingsPage from '@/pages/module/moduleSettings';
|
||||
|
||||
export default [
|
||||
{
|
||||
path: '/module/:slug',
|
||||
component: moduleBase,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: MODULE_PAGE,
|
||||
component: module,
|
||||
meta: {filter: true},
|
||||
},
|
||||
{
|
||||
path: 'submissions/:id',
|
||||
name: SUBMISSIONS_PAGE,
|
||||
component: submissions,
|
||||
meta: {filter: true},
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
name: MODULE_SETTINGS_PAGE,
|
||||
component: settingsPage,
|
||||
},
|
||||
{
|
||||
path: 'visibility',
|
||||
name: VISIBILITY_PAGE,
|
||||
component: moduleVisibility,
|
||||
meta: {
|
||||
layout: 'simple',
|
||||
hideNavigation: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
@ -204,9 +204,6 @@ export default new Vuex.Store({
|
|||
setScrollPosition(state, payload) {
|
||||
state.scrollPosition = payload;
|
||||
},
|
||||
setNewContentBlock(state, payload) {
|
||||
state.newContentBlock = payload;
|
||||
},
|
||||
setContentBlockPosition(state, payload) {
|
||||
state.contentBlockPosition = payload;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -211,3 +211,32 @@
|
|||
line-height: $default-heading-line-height;
|
||||
margin-top: -0.2rem; // to offset the 1.2 line height, it leaves a padding on top
|
||||
}
|
||||
|
||||
@mixin settings-page {
|
||||
padding: $section-spacing;
|
||||
|
||||
@include desktop {
|
||||
width: 800px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
&__page-title {
|
||||
@include main-title;
|
||||
margin-bottom: $section-spacing;
|
||||
}
|
||||
|
||||
&__heading {
|
||||
@include heading-1;
|
||||
margin-bottom: $medium-spacing;
|
||||
}
|
||||
|
||||
&__paragraph {
|
||||
@include lead-paragraph;
|
||||
}
|
||||
|
||||
&__section {
|
||||
margin-bottom: $section-spacing;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ $list-height: 52px;
|
|||
|
||||
$default-border-radius: 13px;
|
||||
$input-border-radius: 3px;
|
||||
$round-border-radius: 32px;
|
||||
|
||||
//modal stuff
|
||||
$modal-lateral-padding: 34px;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -24,7 +24,6 @@ from rooms.mutations import RoomMutations
|
|||
from rooms.schema import RoomsQuery, ModuleRoomsQuery
|
||||
from users.schema import AllUsersQuery, UsersQuery
|
||||
from users.mutations import ProfileMutations
|
||||
from registration.mutations_public import RegistrationMutations
|
||||
|
||||
|
||||
class CustomQuery(UsersQuery, AllUsersQuery, ModuleRoomsQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery,
|
||||
|
|
@ -36,7 +35,7 @@ class CustomQuery(UsersQuery, AllUsersQuery, ModuleRoomsQuery, RoomsQuery, Objec
|
|||
|
||||
|
||||
class CustomMutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, CoreMutations, PortfolioMutations,
|
||||
ProfileMutations, SurveyMutations, NoteMutations, RegistrationMutations, SpellCheckMutations,
|
||||
ProfileMutations, SurveyMutations, NoteMutations, SpellCheckMutations,
|
||||
CouponMutations, graphene.ObjectType):
|
||||
if settings.DEBUG:
|
||||
debug = graphene.Field(DjangoDebug, name='_debug')
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ from books.blocks import DEFAULT_RICH_TEXT_FEATURES
|
|||
from core.wagtail_utils import StrictHierarchyPage
|
||||
from users.models import SchoolClass
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Module(StrictHierarchyPage):
|
||||
class Meta:
|
||||
|
|
@ -60,6 +58,34 @@ class Module(StrictHierarchyPage):
|
|||
def get_child_ids(self):
|
||||
return self.get_children().values_list('id', flat=True)
|
||||
|
||||
def sync_from_school_class(self, school_class_template, school_class_to_sync):
|
||||
# import here so we don't get a circular import error
|
||||
from books.models import Chapter, ContentBlock
|
||||
|
||||
# get chapters of module
|
||||
chapters = Chapter.get_by_parent(self)
|
||||
content_block_query = ContentBlock.objects.none()
|
||||
|
||||
# get content blocks of chapters
|
||||
for chapter in chapters:
|
||||
content_block_query = content_block_query.union(ContentBlock.get_by_parent(chapter))
|
||||
|
||||
# clear all `hidden for` and `visible for` for class `school_class_to_sync`
|
||||
for content_block in school_class_to_sync.hidden_content_blocks.intersection(content_block_query):
|
||||
content_block.hidden_for.remove(school_class_to_sync)
|
||||
for content_block in school_class_to_sync.visible_content_blocks.intersection(content_block_query):
|
||||
content_block.visible_for.remove(school_class_to_sync)
|
||||
|
||||
# get all content blocks with `hidden for` for class `school_class_pattern`
|
||||
for content_block in school_class_template.hidden_content_blocks.intersection(content_block_query):
|
||||
# add `school_class_to_sync` to these blocks' `hidden for`
|
||||
content_block.hidden_for.add(school_class_to_sync)
|
||||
|
||||
# get all content blocks with `visible for` for class `school_class_pattern`
|
||||
for content_block in school_class_template.visible_content_blocks.intersection(content_block_query):
|
||||
# add `school_class_to_sync` to these blocks' `visible for`
|
||||
content_block.visible_for.add(school_class_to_sync)
|
||||
|
||||
|
||||
class RecentModule(models.Model):
|
||||
module = models.ForeignKey(Module, on_delete=models.CASCADE, related_name='recent_modules')
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from books.schema.mutations.chapter import UpdateChapterVisibility
|
||||
from books.schema.mutations.contentblock import MutateContentBlock, AddContentBlock, DeleteContentBlock
|
||||
from books.schema.mutations.module import UpdateSolutionVisibility, UpdateLastModule, UpdateLastTopic
|
||||
from books.schema.mutations.module import UpdateSolutionVisibility, UpdateLastModule, SyncModuleVisibility
|
||||
from books.schema.mutations.topic import UpdateLastTopic
|
||||
|
||||
|
||||
class BookMutations(object):
|
||||
|
|
@ -11,3 +12,4 @@ class BookMutations(object):
|
|||
update_last_module = UpdateLastModule.Field()
|
||||
update_last_topic = UpdateLastTopic.Field()
|
||||
update_chapter_visibility = UpdateChapterVisibility.Field()
|
||||
sync_module_visibility = SyncModuleVisibility.Field()
|
||||
|
|
|
|||
|
|
@ -3,9 +3,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, RecentModule
|
||||
from books.schema.queries import ModuleNode, TopicNode
|
||||
from api.utils import get_object
|
||||
from books.models import Module, RecentModule
|
||||
from books.schema.queries import ModuleNode
|
||||
from users.models import SchoolClass
|
||||
|
||||
|
||||
class UpdateSolutionVisibility(relay.ClientIDMutation):
|
||||
|
|
@ -75,23 +76,30 @@ class UpdateLastModule(relay.ClientIDMutation):
|
|||
return cls(last_module=last_module.module)
|
||||
|
||||
|
||||
class UpdateLastTopic(relay.ClientIDMutation):
|
||||
class SyncModuleVisibility(relay.ClientIDMutation):
|
||||
class Input:
|
||||
# todo: use slug here too
|
||||
id = graphene.ID()
|
||||
module = graphene.String(required=True)
|
||||
template_school_class = graphene.ID(required=True)
|
||||
school_class = graphene.ID(required=True)
|
||||
|
||||
topic = graphene.Field(TopicNode)
|
||||
success = graphene.Boolean()
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **args):
|
||||
user = info.context.user
|
||||
id = args.get('id')
|
||||
if not user.is_teacher():
|
||||
raise Exception('Permission denied')
|
||||
|
||||
topic = get_object(Topic, id)
|
||||
if not topic:
|
||||
raise Topic.DoesNotExist
|
||||
module_slug = args.get('module')
|
||||
template_id = args.get('template_school_class')
|
||||
school_class_id = args.get('school_class')
|
||||
|
||||
user.last_topic = topic
|
||||
user.save()
|
||||
module = Module.objects.get(slug=module_slug)
|
||||
template = get_object(SchoolClass, template_id)
|
||||
school_class = get_object(SchoolClass, school_class_id)
|
||||
if not template.is_user_in_schoolclass(user) or not school_class.is_user_in_schoolclass(user):
|
||||
raise Exception('Permission denied')
|
||||
|
||||
return cls(topic=topic)
|
||||
module.sync_from_school_class(template, school_class)
|
||||
|
||||
return cls(success=True)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
import graphene
|
||||
from graphene import relay
|
||||
|
||||
from api.utils import get_object
|
||||
from books.models import Topic
|
||||
from books.schema.queries import TopicNode
|
||||
|
||||
|
||||
class UpdateLastTopic(relay.ClientIDMutation):
|
||||
class Input:
|
||||
# todo: use slug here too
|
||||
id = graphene.ID()
|
||||
|
||||
topic = graphene.Field(TopicNode)
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **args):
|
||||
user = info.context.user
|
||||
id = args.get('id')
|
||||
|
||||
topic = get_object(Topic, id)
|
||||
if not topic:
|
||||
raise Topic.DoesNotExist
|
||||
|
||||
user.last_topic = topic
|
||||
user.save()
|
||||
|
||||
return cls(topic=topic)
|
||||
|
|
@ -199,7 +199,7 @@ class TopicNode(DjangoObjectType):
|
|||
class Meta:
|
||||
model = Topic
|
||||
only_fields = [
|
||||
'slug', 'title', 'meta_title', 'teaser', 'description', 'vimeo_id', 'order', 'instructions'
|
||||
'slug', 'title', 'teaser', 'description', 'vimeo_id', 'order', 'instructions'
|
||||
]
|
||||
filter_fields = {
|
||||
'slug': ['exact', 'icontains', 'in'],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,193 @@
|
|||
import logging
|
||||
|
||||
from django.test import TestCase, RequestFactory
|
||||
from graphene.test import Client
|
||||
from graphql_relay import to_global_id
|
||||
|
||||
from api.schema import schema
|
||||
from books.models import ContentBlock, Chapter
|
||||
from books.factories import ModuleFactory
|
||||
from users.factories import SchoolClassFactory
|
||||
from users.models import User
|
||||
from users.services import create_users
|
||||
|
||||
CONTENT_BLOCK_QUERY = """
|
||||
query ContentBlockQuery($id: ID!) {
|
||||
contentBlock(id: $id) {
|
||||
hiddenFor {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
visibleFor {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
SYNC_MUTATION = """
|
||||
mutation SyncMutationVisibility($input: SyncModuleVisibilityInput!) {
|
||||
syncModuleVisibility(input: $input) {
|
||||
success
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
class CopyVisibilityForClassesTestCase(TestCase):
|
||||
"""
|
||||
what do we want to happen?
|
||||
we have 3 public content blocks [X, Y, Z]
|
||||
we have 2 custom content block [M, N]
|
||||
we have 2 school classes [A, B]
|
||||
one public content block is hidden for class A | [X, Y]
|
||||
one custom content block is visible for class A | [M]
|
||||
class B also sees two of three public content blocks, but one is different from what A sees | [X, Z]
|
||||
class B doesn't see the custom content block, but another one | [N]
|
||||
so A sees | [X, Y, M]
|
||||
B sees | [X, Z, N]
|
||||
we want to copy the settings from class A to class B
|
||||
now class B sees the same content blocks as class A | [X, Y, N]
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
module = ModuleFactory(slug='some-module')
|
||||
chapter = Chapter(title='Some Chapter')
|
||||
module.add_child(instance=chapter)
|
||||
create_users()
|
||||
teacher = User.objects.get(username='teacher')
|
||||
student1 = User.objects.get(username='student1')
|
||||
student2 = User.objects.get(username='student2')
|
||||
# school class to be used as the pattern or model
|
||||
template_school_class = SchoolClassFactory(name='template-class', users=[teacher, student1])
|
||||
# school class to be synced, e.g. adapted to be like the other
|
||||
school_class_to_be_synced = SchoolClassFactory(name='class-to-be-synced', users=[teacher, student2])
|
||||
|
||||
default_content_block = ContentBlock(title='default block', slug='default')
|
||||
hidden_content_block = ContentBlock(title='hidden block', slug='hidden')
|
||||
other_hidden_content_block = ContentBlock(title='other hidden block', slug='other-hidden')
|
||||
custom_content_block = ContentBlock(title='custom block', slug='custom', owner=teacher)
|
||||
other_custom_content_block = ContentBlock(title='other custom block', slug='other-custom', owner=teacher)
|
||||
|
||||
chapter.specific.add_child(instance=default_content_block)
|
||||
chapter.specific.add_child(instance=hidden_content_block)
|
||||
chapter.specific.add_child(instance=custom_content_block)
|
||||
chapter.specific.add_child(instance=other_custom_content_block)
|
||||
chapter.specific.add_child(instance=other_hidden_content_block)
|
||||
|
||||
hidden_content_block.hidden_for.add(template_school_class)
|
||||
custom_content_block.visible_for.add(template_school_class)
|
||||
|
||||
other_hidden_content_block.hidden_for.add(school_class_to_be_synced)
|
||||
other_custom_content_block.visible_for.add(school_class_to_be_synced)
|
||||
|
||||
teacher_request = RequestFactory().get('/')
|
||||
teacher_request.user = teacher
|
||||
self.teacher_client = Client(schema=schema, context_value=teacher_request)
|
||||
|
||||
student1_request = RequestFactory().get('/')
|
||||
student1_request.user = student1
|
||||
self.student1_client = Client(schema=schema, context_value=student1_request)
|
||||
|
||||
student2_request = RequestFactory().get('/')
|
||||
student2_request.user = student2
|
||||
self.student2_client = Client(schema=schema, context_value=student2_request)
|
||||
|
||||
self.template_school_class = template_school_class
|
||||
self.school_class_to_be_synced = school_class_to_be_synced
|
||||
self.module = module
|
||||
self.chapter = to_global_id('ChapterNode', chapter.pk)
|
||||
self.default_content_block = to_global_id('ContentBlockNode', default_content_block.pk)
|
||||
self.hidden_content_block = to_global_id('ContentBlockNode', hidden_content_block.pk)
|
||||
self.custom_content_block = to_global_id('ContentBlockNode', custom_content_block.pk)
|
||||
self.other_custom_content_block = to_global_id('ContentBlockNode', other_custom_content_block.pk)
|
||||
self.other_hidden_content_block = to_global_id('ContentBlockNode', other_hidden_content_block.pk)
|
||||
|
||||
def _get_result(self, query, client, id):
|
||||
result = client.execute(query, variables={
|
||||
'id': id
|
||||
})
|
||||
return result
|
||||
|
||||
def _test_in_sync(self):
|
||||
# the hidden block is hidden for both now
|
||||
hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.hidden_content_block)
|
||||
hidden_for = hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges')
|
||||
self.assertTrue('template-class' in map(lambda x: x['node']['name'], hidden_for))
|
||||
self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], hidden_for))
|
||||
|
||||
# the other hidden block is hidden for no one now
|
||||
other_hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client,
|
||||
self.other_hidden_content_block)
|
||||
hidden_for = other_hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges')
|
||||
self.assertEqual(len(hidden_for), 0)
|
||||
|
||||
# the default block is still hidden for no one
|
||||
default_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.default_content_block)
|
||||
hidden_for = default_result.get('data').get('contentBlock').get('hiddenFor').get('edges')
|
||||
self.assertEqual(len(hidden_for), 0)
|
||||
|
||||
# the custom block is visible for both
|
||||
custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.custom_content_block)
|
||||
visible_for = custom_result.get('data').get('contentBlock').get('visibleFor').get('edges')
|
||||
self.assertTrue('template-class' in map(lambda x: x['node']['name'], visible_for))
|
||||
self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], visible_for))
|
||||
|
||||
# the other custom block is visible for no one
|
||||
other_custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client,
|
||||
self.other_custom_content_block)
|
||||
visible_for = other_custom_result.get('data').get('contentBlock').get('visibleFor').get('edges')
|
||||
self.assertEqual(len(visible_for), 0)
|
||||
|
||||
def test_hidden_for_and_visible_for_set_correctly(self):
|
||||
self.assertEqual(ContentBlock.objects.count(), 5)
|
||||
|
||||
hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.hidden_content_block)
|
||||
hidden_for = hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges')
|
||||
self.assertTrue('template-class' in map(lambda x: x['node']['name'], hidden_for))
|
||||
self.assertFalse('class-to-be-synced' in map(lambda x: x['node']['name'], hidden_for))
|
||||
|
||||
other_hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client,
|
||||
self.other_hidden_content_block)
|
||||
hidden_for = other_hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges')
|
||||
self.assertFalse('template-class' in map(lambda x: x['node']['name'], hidden_for))
|
||||
self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], hidden_for))
|
||||
|
||||
default_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.default_content_block)
|
||||
hidden_for = default_result.get('data').get('contentBlock').get('hiddenFor').get('edges')
|
||||
self.assertEqual(len(hidden_for), 0)
|
||||
|
||||
custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.custom_content_block)
|
||||
visible_for = custom_result.get('data').get('contentBlock').get('visibleFor').get('edges')
|
||||
self.assertTrue('template-class' in map(lambda x: x['node']['name'], visible_for))
|
||||
self.assertFalse('class-to-be-synced' in map(lambda x: x['node']['name'], visible_for))
|
||||
|
||||
other_custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client,
|
||||
self.other_custom_content_block)
|
||||
visible_for = other_custom_result.get('data').get('contentBlock').get('visibleFor').get('edges')
|
||||
self.assertFalse('template-class' in map(lambda x: x['node']['name'], visible_for))
|
||||
self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], visible_for))
|
||||
|
||||
def test_syncs_correctly(self):
|
||||
self.module.sync_from_school_class(self.template_school_class, self.school_class_to_be_synced)
|
||||
|
||||
self._test_in_sync()
|
||||
|
||||
def test_mutation(self):
|
||||
self.teacher_client.execute(SYNC_MUTATION, variables={
|
||||
'input': {
|
||||
'module': self.module.slug,
|
||||
'templateSchoolClass': to_global_id('SchoolClassNode', self.template_school_class.pk),
|
||||
'schoolClass': to_global_id('SchoolClassNode', self.school_class_to_be_synced.pk)
|
||||
}
|
||||
})
|
||||
self._test_in_sync()
|
||||
|
|
@ -335,6 +335,11 @@ LOGGING = {
|
|||
'level': 'WARNING',
|
||||
'propagate': True,
|
||||
},
|
||||
'wagtail': {
|
||||
'handlers': ['console'],
|
||||
'level': 'WARNING',
|
||||
'propagate': False,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -348,25 +353,6 @@ if not DEBUG and os.environ.get('SENTRY_DSN'):
|
|||
send_default_pii=True
|
||||
)
|
||||
|
||||
# LOGGING['handlers'] = {
|
||||
# 'sentry': {
|
||||
# 'level': 'ERROR', # ERROR, WARNING, INFO
|
||||
# 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
|
||||
# 'tags': {'custom-tag': 'x'},
|
||||
# },
|
||||
# 'console': {
|
||||
# 'level': 'INFO',
|
||||
# 'class': 'logging.StreamHandler',
|
||||
# 'stream': sys.stdout,
|
||||
# 'formatter': 'simple_format'
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# for k, v in LOGGING['loggers'].items():
|
||||
# LOGGING['loggers'][k]['handlers'] = ['sentry', 'console']
|
||||
# else:
|
||||
# RAVEN_CONFIG = {}
|
||||
|
||||
RAVEN_DSN_JS = os.environ.get('RAVEN_DSN_JS', '')
|
||||
|
||||
GOOGLE_TAG_MANAGER_CONTAINER_ID = os.environ.get('GOOGLE_TAG_MANAGER_CONTAINER_ID')
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"schema": {
|
||||
"request": {
|
||||
"url": "http://localhost:8000/graphql",
|
||||
"method": "POST",
|
||||
"postIntrospectionQuery": true,
|
||||
"options": {
|
||||
"headers": {
|
||||
"user-agent": "JS GraphQL"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"endpoints": [
|
||||
{
|
||||
"name": "Default (http://localhost:8000/graphql",
|
||||
"url": "http://localhost:8000/graphql",
|
||||
"options": {
|
||||
"headers": {
|
||||
"user-agent": "JS GraphQL"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ from django.contrib.sessions.middleware import SessionMiddleware
|
|||
from django.test import TestCase, RequestFactory
|
||||
from graphene.test import Client
|
||||
|
||||
from api.schema import schema
|
||||
from api.schema_public import schema
|
||||
from core.hep_client import HepClient
|
||||
from core.tests.mock_hep_data_factory import ME_DATA, VALID_TEACHERS_ORDERS
|
||||
from users.models import License
|
||||
|
|
|
|||
Loading…
Reference in New Issue