Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cypress.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"baseUrl": "http://localhost:3000",
"defaultCommandTimeout": 45000,
"defaultCommandTimeout": 60000,
"video": false
}
154 changes: 154 additions & 0 deletions cypress/integration/tutorial-redirect-modal.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import tutorialsList, { getTutorialType } from '../../src/utils/tutorials'

function visit (url, { referrer, clearStorage = true } = {}) {
cy.visit(url, {
onBeforeLoad (window) {
referrer &&
Object.defineProperty(window.document, 'referrer', { get () { return referrer } })

clearStorage &&
window.localStorage.clear()
}
})
}

// Asserts

function assertModalShows () {
cy.get('.modal-overlay', { timeout: 5e3 }).should('exist')
cy.get('[data-cy="tutorial-redirect-modal"', { timeout: 5e3 }).should('be.visible')
cy.get('.home', { timeout: 5e3 }).should('be.visible') // still visible behind the overlay
}

function assertModalContent ({ tutorial, lessonId, context = 'start' }) {
const lesson = tutorial && tutorial.lessons.find(lesson => lesson.id === lessonId)

cy.get('.modal-content')
.should('contain.text', tutorial.title)
.should('contain.text', lesson.title)

if (context === 'start') {
// check content matches the start context
cy.get('.modal-content', { timeout: 5e3 }).should('contain', 'start this tutorial')
} else if (context === 'resume') {
cy.get('.modal-content', { timeout: 5e3 }).should('contain', 'resume the tutorial')
}
}

function assertModalActions ({ action = 'start' } = {}) {
if (action === 'start') {
cy.get('[data-cy="tutorial-redirect-modal-action-start"]', { timeout: 5e3 }).should('exist')
} else if (action === 'resume') {
cy.get('[data-cy="tutorial-redirect-modal-action-resume"]', { timeout: 5e3 }).should('exist')
}
}

function assertModalDoesNotShow () {
cy.get('[data-cy="tutorial-redirect-modal"', { timeout: 5e3 }).should('not.exist')
cy.get('.modal-overlay', { timeout: 5e3 }).should('not.exist')
cy.get('.home', { timeout: 5e3 }).should('be.visible')
}

describe('TUTORIAL REDIRECT MODAL', () => {
let tutorial

before(() => {
// get first text based tutorial
tutorial = tutorialsList[Object.keys(tutorialsList).find(tutorialFormattedId => getTutorialType(tutorialFormattedId) === 'text')]
})

it('should show when landing in the middle of a tutorial from a search engine', () => {
visit(`/${tutorial.url}/03`, { referrer: 'https://google.com/' })
assertModalShows()
assertModalContent({ tutorial, lessonId: 3 })

visit(`/${tutorial.url}/04/`, { referrer: 'https://bing.com/' })
assertModalShows()
assertModalContent({ tutorial, lessonId: 4 })
})

it('should show when landing in the middle of a tutorial from a search engine and show the resume option when some past lessons have been passed', () => {
visit(`/${tutorial.url}/01`, { referrer: '' })
cy.get('button[data-cy="next-lesson-text"]').click()
cy.get('button[data-cy="next-lesson-text"]').click()
visit(`/${tutorial.url}/05`, { referrer: 'https://google.com', clearStorage: false })
assertModalShows()
assertModalContent({ tutorial, lessonId: 5, context: 'resume' })
assertModalActions({ action: 'resume' })
})

it('should close when using close button', () => {
visit(`/${tutorial.url}/02`, { referrer: 'https://google.com' })
assertModalShows()
assertModalContent({ tutorial, lessonId: 2 })
cy.get('button.close').click()
assertModalDoesNotShow()
})

it('should close when using view lesson button', () => {
visit(`/${tutorial.url}/02`, { referrer: 'https://google.com' })
assertModalShows()
assertModalContent({ tutorial, lessonId: 2 })
cy.get('button[data-cy="tutorial-redirect-modal-view-lesson"').click()
assertModalDoesNotShow()
})

it('should not show when referrer is empty', () => {
visit(`/${tutorial.url}/02`, { referrer: '' })
assertModalDoesNotShow()
})

it('should not show when opening lesson and the referrer is not one of the configured search engines', () => {
visit(`/${tutorial.url}/03`, { referrer: 'https://searchengine.com/' })
assertModalDoesNotShow()
})

it('should not show when opening lesson from app link', () => {
visit(`/${tutorial.url}`, { referrer: 'http://localhost:3000/' })
cy.get(`a[href="/${tutorial.url}/02"][data-cy="lesson-link-standard"]`).click()
assertModalDoesNotShow()
})

it('should not show when opening the first lesson', () => {
visit(`/${tutorial.url}/01`, { referrer: 'https://google.com/' })
assertModalDoesNotShow()
})

it('should not show when opening resources lesson', () => {
visit(`/${tutorial.url}/resources`, { referrer: 'https://google.com/' })
assertModalDoesNotShow()
})

it('should not show when opening a lesson the user already passed', () => {
visit(`/${tutorial.url}/02`, { referrer: 'https://google.com' })
assertModalShows()
assertModalContent({ tutorial, lessonId: 2 })
cy.get('button.close').click()
cy.get('button[data-cy="next-lesson-text"]').click()
visit(`/${tutorial.url}/02`, { referrer: 'https://google.com', clearStorage: false })
assertModalDoesNotShow()
})

it('should not show a second time after starting a tutorial and passing the lesson', () => {
visit(`/${tutorial.url}/01`, { referrer: 'https://google.com' })
assertModalDoesNotShow()
cy.get('button[data-cy="next-lesson-text"]').click()
visit(`/${tutorial.url}/03`, { referrer: 'https://google.com', clearStorage: false })
assertModalShows()
assertModalContent({ tutorial, lessonId: 3, context: 'resume' })
assertModalActions({ action: 'resume' })
cy.get(`[data-cy="tutorial-redirect-modal-action-resume"]`).click()
cy.location('pathname').should('eq', `/${tutorial.url}/02`)
assertModalDoesNotShow()
cy.get('button[data-cy="next-lesson-text"]').click()
cy.location('pathname').should('eq', `/${tutorial.url}/03`)
assertModalDoesNotShow()
})

it('should always show when using the query parameter _forceShowRedirectModal=true', () => {
visit(`/${tutorial.url}/01?_forceShowRedirectModal=true`)
assertModalShows()
assertModalContent({ tutorial, lessonId: 1, context: 'start' })
assertModalActions({ action: 'start' })
})
})
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"monaco-editor-webpack-plugin": "^1.9.0",
"new-github-issue-url": "^0.2.1",
"p-timeout": "^3.2.0",
"portal-vue": "^2.1.7",
"prerender-spa-plugin": "^3.4.0",
"querystringify": "^2.1.1",
"raw-loader": "^0.5.1",
Expand Down
1 change: 1 addition & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<template>
<div id="app">
<router-view :key="$route.path"></router-view>
<portal-target name="modal"></portal-target>
</div>
</template>

Expand Down
1 change: 1 addition & 0 deletions src/api/debug.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') && process.env.DEBUG
2 changes: 1 addition & 1 deletion src/api/modules/lessons.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const errorCode = require('err-code')
const marked = require('meta-marked')

const log = require('../logger')
const debug = require('../../utils/debug')
const debug = require('../debug')
const config = require('../config')

const logGroup = log.createLogGroup('lessons')
Expand Down
2 changes: 1 addition & 1 deletion src/api/modules/resources.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const log = require('../logger')
const debug = require('../../utils/debug')
const debug = require('../debug')
const tutorialsApi = require('./tutorials')
const utils = require('../utils')

Expand Down
2 changes: 1 addition & 1 deletion src/api/modules/tutorials.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const _ = require('lodash')
const del = require('del')

const log = require('../logger')
const debug = require('../../utils/debug')
const debug = require('../debug')
const config = require('../config')
const utils = require('../utils')
const lessonsApi = require('./lessons')
Expand Down
5 changes: 4 additions & 1 deletion src/components/Lesson.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<div :class="{'overflow-hidden': expandChallenge}">
<TutorialRedirectModal :tutorial="tutorial" :lesson="lesson" />
<Header/>
<div class="container center-l mw7-l ph3">
<section class="mw7 center mt3 pt2">
Expand All @@ -11,7 +12,7 @@
:lessonsInTutorial="lessonsInTutorial"
:lessonPassed="lessonPassed" />
<TypeIcon
:lessonId="isResources? 'resources' : lessonId"
:lessonId="isResources ? 'resources' : lessonId"
:tutorialId="tutorial.formattedId"
class="h2 ml3" />
</div>
Expand Down Expand Up @@ -153,6 +154,7 @@ import Output from './Output.vue'
import Info from './Info.vue'
import Validator from './Validator.vue'
import TutorialCompletionCallout from './callouts/TutorialCompletion.vue'
import TutorialRedirectModal from './modals/TutorialRedirectModal.vue'
import TypeIcon from './TypeIcon.vue'

const MAX_EXEC_TIMEOUT = isProduction ? 10000 : 60000
Expand Down Expand Up @@ -221,6 +223,7 @@ export default {
Info,
Validator,
TutorialCompletionCallout,
TutorialRedirectModal,
TypeIcon
},
props: {
Expand Down
7 changes: 5 additions & 2 deletions src/components/buttons/Button.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
:data-loading="loading"
:type="type"
:disabled="loading || disabled"
:data-cy="dataCy"
>
<span class="loader"></span>
<span class="text">{{text}}</span>
Expand Down Expand Up @@ -39,7 +40,8 @@ export default {
blur: {
type: Function,
default: () => {}
}
},
dataCy: String
}
}
</script>
Expand All @@ -48,7 +50,8 @@ export default {
button {
position: relative;
opacity: 0.9;
min-width: 120px;
min-width: 7.5rem;
min-height: 2.5rem;
box-shadow: inset 0 0 8px rgb(0 0 0 / 0%);
outline: none;

Expand Down
9 changes: 5 additions & 4 deletions src/components/buttons/ButtonClose.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ export default {
Parent needs to have position != static
*/
button.close {
--position: 0.4rem;

position: absolute;
top: 1rem;
right: 1rem;
top: var(--position);
right: var(--position);
transform: scale(0.95);

padding: 0.5rem;
Expand Down Expand Up @@ -65,8 +67,7 @@ button.close {

@media screen and (max-width: 30rem) {
button.close {
top: 0.3rem;
right: 0.3rem;
--position: 0.3rem;

opacity: 0.8;
transform: scale(0.8);
Expand Down
10 changes: 7 additions & 3 deletions src/components/buttons/ButtonLink.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
:to="link ? { name: link } : to"
class="inline-flex justify-center avenir dib v-mid fw7 nowrap lh-copy bn br1 pointer bg-navy white outline-focus pv2 ph3"
:disabled="disabled"
@click="onClick"
@click.native="onClick"
:data-cy="dataCy"
>
<slot>{{text}}</slot>
</router-link>
Expand All @@ -14,6 +15,7 @@
target="__blank"
@click="onClick"
class="inline-flex justify-center avenir dib v-mid fw7 nowrap lh-copy bn br1 pointer bg-navy white outline-focus pv2 ph3"
:data-cy="dataCy"
>
<slot>{{text}}</slot>
</a>
Expand All @@ -38,7 +40,8 @@ export default {
onClick: {
type: Function,
default: () => {}
}
},
dataCy: String
}
}
</script>
Expand All @@ -47,7 +50,8 @@ export default {
a {
position: relative;
opacity: 0.9;
min-width: 120px;
min-width: 7.5rem;
min-height: 2.5rem;
box-shadow: inset 0 0 8px rgb(0 0 0 / 0%);
outline: none;
user-select: none;
Expand Down
Loading