Merge branch 'master' into add-styleguide-dev-mode

This commit is contained in:
Grzegorz Leoniec 2019-02-28 17:41:07 +01:00 committed by GitHub
commit d6a44edb7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 219 additions and 172 deletions

View File

@ -1,2 +1 @@
JWT_SECRET="b/&&7b78BF&fv/Vd"
MAPBOX_TOKEN="pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ"

View File

@ -2,7 +2,6 @@ import { When, Then } from 'cypress-cucumber-preprocessor/steps'
/* global cy */
When('I navigate to the administration dashboard', () => {
cy.get('.avatar-menu').click()
cy.get('.avatar-menu-popover')
@ -14,11 +13,15 @@ Then('I can see a list of categories ordered by post count:', table => {
cy.get('thead')
.find('tr th')
.should('have.length', 3)
table.hashes().forEach(({Name, Posts}, index) => {
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(2)`)
.should('contain', Name)
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(3)`)
.should('contain', Posts)
table.hashes().forEach(({ Name, Posts }, index) => {
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(2)`).should(
'contain',
Name
)
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(3)`).should(
'contain',
Posts
)
})
})
@ -26,12 +29,18 @@ Then('I can see a list of tags ordered by user count:', table => {
cy.get('thead')
.find('tr th')
.should('have.length', 4)
table.hashes().forEach(({Name, Users, Posts}, index) => {
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(2)`)
.should('contain', Name)
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(3)`)
.should('contain', Users)
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(4)`)
.should('contain', Posts)
table.hashes().forEach(({ Name, Users, Posts }, index) => {
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(2)`).should(
'contain',
Name
)
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(3)`).should(
'contain',
Users
)
cy.get(`tbody > :nth-child(${index + 1}) > :nth-child(4)`).should(
'contain',
Posts
)
})
})

View File

@ -111,7 +111,7 @@ Then(`I can't see the moderation menu item`, () => {
.should('not.exist')
})
When(/^I confirm the reporting dialog .*:$/, (message) => {
When(/^I confirm the reporting dialog .*:$/, message => {
cy.contains(message) // wait for element to become visible
cy.get('.ds-modal').within(() => {
cy.get('button')

View File

@ -15,7 +15,7 @@ beforeEach(async () => {
})
Cypress.Commands.add('factory', () => {
return Factory({seedServerHost})
return Factory({ seedServerHost })
})
Cypress.Commands.add(

View File

@ -14,7 +14,6 @@ services:
environment:
- HOST=0.0.0.0
- BACKEND_URL=http://backend:4000
- JWT_SECRET=b/&&7b78BF&fv/Vd
- MAPBOX_TOKEN="pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.bZ8KK9l70omjXbEkkbHGsQ"
networks:

View File

@ -18,6 +18,7 @@
"nonGlobalStepDefinitions": true
},
"jest": {
"verbose": true,
"moduleFileExtensions": [
"js",
"json",

View File

@ -48,101 +48,90 @@
<script>
import gql from 'graphql-tag'
import { mapGetters } from 'vuex'
import { mapGetters, mapMutations } from 'vuex'
import { CancelToken } from 'axios'
import find from 'lodash/find'
let timeout
const mapboxToken = process.env.MAPBOX_TOKEN
const query = gql`
query getUser($id: ID) {
User(id: $id) {
id
name
locationName
about
}
}
`
const mutation = gql`
mutation($id: ID!, $name: String, $locationName: String, $about: String) {
UpdateUser(
id: $id
name: $name
locationName: $locationName
about: $about
) {
id
name
locationName
about
}
}
`
export default {
data() {
return {
axiosSource: null,
cities: [],
sending: false,
form: {
name: null,
locationName: null,
about: null
}
formData: {}
}
},
computed: {
...mapGetters({
user: 'auth/user'
})
},
watch: {
user: {
immediate: true,
handler: function(user) {
this.form = {
name: user.name,
locationName: user.locationName,
about: user.about
}
currentUser: 'auth/user'
}),
form: {
get: function() {
const { name, locationName, about } = this.currentUser
return { name, locationName, about }
},
set: function(formData) {
this.formData = formData
}
}
},
methods: {
...mapMutations({
setCurrentUser: 'auth/SET_USER'
}),
submit() {
this.sending = true
const { name, about } = this.formData
let { locationName } = this.formData
locationName = locationName && (locationName['label'] || locationName)
this.$apollo
.mutate({
mutation: gql`
mutation(
$id: ID!
$name: String
$locationName: String
$about: String
) {
UpdateUser(
id: $id
name: $name
locationName: $locationName
about: $about
) {
id
name
locationName
about
}
}
`,
// Parameters
mutation,
variables: {
id: this.user.id,
name: this.form.name,
locationName: this.form.locationName
? this.form.locationName['label'] || this.form.locationName
: null,
about: this.form.about
id: this.currentUser.id,
name,
locationName,
about
},
// Update the cache with the result
// The query will be updated with the optimistic response
// and then with the real result of the mutation
update: (store, { data: { UpdateUser } }) => {
this.$store.dispatch('auth/fetchCurrentUser')
// Read the data from our cache for this query.
// const data = store.readQuery({ query: TAGS_QUERY })
// Add our tag from the mutation to the end
// data.tags.push(addTag)
// Write our data back to the cache.
// store.writeQuery({ query: TAGS_QUERY, data })
const { name, locationName, about } = UpdateUser
this.setCurrentUser({
...this.currentUser,
name,
locationName,
about
})
}
// Optimistic UI
// Will be treated as a 'fake' result as soon as the request is made
// so that the UI can react quickly and the user be happy
/* optimisticResponse: {
__typename: 'Mutation',
addTag: {
__typename: 'Tag',
id: -1,
label: newTag
}
} */
})
.then(data => {
this.$toast.success('Updated user')

View File

@ -57,90 +57,70 @@ export const actions = {
if (!token) {
return
}
const payload = await jwt.verify(token, process.env.JWT_SECRET)
if (!payload.id) {
return
}
commit('SET_TOKEN', token)
commit('SET_USER', {
id: payload.id
})
await dispatch('fetchCurrentUser')
},
async check({ commit, dispatch, getters }) {
if (!this.app.$apolloHelpers.getToken()) {
await dispatch('logout')
}
return getters.isLoggedIn
},
async fetchCurrentUser({ commit, getters }) {
await this.app.apolloProvider.defaultClient
.query({
query: gql(`
query User($id: ID!) {
User(id: $id) {
id
name
slug
email
avatar
role
locationName
about
}
async fetchCurrentUser({ commit, dispatch }) {
const client = this.app.apolloProvider.defaultClient
const {
data: { currentUser }
} = await client.query({
query: gql(`{
currentUser {
id
name
slug
email
avatar
role
about
locationName
}
`),
variables: { id: getters.user.id }
})
.then(({ data }) => {
const user = data.User.pop()
if (user.id && user.email) {
commit('SET_USER', user)
}
})
return getters.user
}`)
})
if (!currentUser) return dispatch('logout')
commit('SET_USER', currentUser)
return currentUser
},
async login({ commit }, { email, password }) {
async login({ commit, dispatch }, { email, password }) {
commit('SET_PENDING', true)
try {
const res = await this.app.apolloProvider.defaultClient
.mutate({
mutation: gql(`
const client = this.app.apolloProvider.defaultClient
const {
data: { login }
} = await client.mutate({
mutation: gql(`
mutation($email: String!, $password: String!) {
login(email: $email, password: $password) {
id
name
slug
email
avatar
role
locationName
about
token
}
login(email: $email, password: $password)
}
`),
variables: { email, password }
})
.then(({ data }) => data && data.login)
await this.app.$apolloHelpers.onLogin(res.token)
commit('SET_TOKEN', res.token)
const userData = Object.assign({}, res)
delete userData.token
commit('SET_USER', userData)
variables: { email, password }
})
await this.app.$apolloHelpers.onLogin(login)
commit('SET_TOKEN', login)
await dispatch('fetchCurrentUser')
} catch (err) {
throw new Error(err)
} finally {
commit('SET_PENDING', false)
}
},
async logout({ commit }) {
commit('SET_USER', null)
commit('SET_TOKEN', null)
return this.app.$apolloHelpers.onLogout()
},
register(
{ dispatch, commit },
{ email, password, inviteCode, invitedByUserId }

View File

@ -2,23 +2,21 @@ import { getters, mutations, actions } from './auth.js'
let state
let commit
let dispatch
const token =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InUzIiwic2x1ZyI6Implbm55LXJvc3RvY2siLCJuYW1lIjoiSmVubnkgUm9zdG9jayIsImF2YXRhciI6Imh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS91aWZhY2VzL2ZhY2VzL3R3aXR0ZXIvbXV0dV9rcmlzaC8xMjguanBnIiwiZW1haWwiOiJ1c2VyQGV4YW1wbGUub3JnIiwicm9sZSI6InVzZXIiLCJpYXQiOjE1NDUxNDQ2ODgsImV4cCI6MTYzMTU0NDY4OCwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo0MDAwIiwic3ViIjoidTMifQ.s5_JeQN9TaUPfymAXPOpbMAwhmTIg9cnOvNEcj4z75k'
const successfulLoginResponse = {
data: {
login: {
id: 'u3',
name: 'Jenny Rostock',
slug: 'jenny-rostock',
email: 'user@example.org',
avatar:
'https://s3.amazonaws.com/uifaces/faces/twitter/mutu_krish/128.jpg',
role: 'user',
token
}
}
const currentUser = {
id: 'u3',
name: 'Jenny Rostock',
slug: 'jenny-rostock',
email: 'user@example.org',
avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/mutu_krish/128.jpg',
role: 'user'
}
const successfulLoginResponse = { data: { login: token } }
const successfulCurrentUserResponse = { data: { currentUser } }
const incorrectPasswordResponse = {
data: {
login: null
@ -39,6 +37,7 @@ const incorrectPasswordResponse = {
beforeEach(() => {
commit = jest.fn()
dispatch = jest.fn(() => Promise.resolve())
})
describe('getters', () => {
@ -55,34 +54,67 @@ describe('getters', () => {
describe('actions', () => {
let action
describe('login', () => {
describe('given valid credentials and a successful response', () => {
beforeEach(async () => {
const response = Object.assign({}, successfulLoginResponse)
const mutate = jest.fn(() => Promise.resolve(response))
const onLogin = jest.fn(() => Promise.resolve())
const module = {
app: {
apolloProvider: { defaultClient: { mutate } },
$apolloHelpers: { onLogin }
describe('init', () => {
const theAction = () => {
const module = {
app: {
$apolloHelpers: {
getToken: () => token
}
}
action = actions.login.bind(module)
await action(
{ commit },
{ email: 'user@example.org', password: '1234' }
)
}
action = actions.init.bind(module)
return action({ commit, dispatch })
}
describe('client-side', () => {
beforeEach(() => {
process.server = false
})
afterEach(() => {
action = null
it('returns', async () => {
await theAction()
expect(dispatch.mock.calls).toEqual([])
expect(commit.mock.calls).toEqual([])
})
})
describe('server-side', () => {
beforeEach(() => {
process.server = true
})
it('saves the JWT Bearer token', () => {
it('fetches the current user', async () => {
await theAction()
expect(dispatch.mock.calls).toEqual([['fetchCurrentUser']])
})
it('saves the JWT Bearer token', async () => {
await theAction()
expect(commit.mock.calls).toEqual(
expect.arrayContaining([['SET_TOKEN', token]])
)
})
})
})
describe('fetchCurrentUser', () => {
describe('given a successful response', () => {
beforeEach(async () => {
const module = {
app: {
apolloProvider: {
defaultClient: {
query: jest.fn(() =>
Promise.resolve(successfulCurrentUserResponse)
)
}
}
}
}
action = actions.fetchCurrentUser.bind(module)
await action({ commit })
})
it('saves user data without token', () => {
expect(commit.mock.calls).toEqual(
@ -102,6 +134,44 @@ describe('actions', () => {
])
)
})
})
})
describe('login', () => {
describe('given valid credentials and a successful response', () => {
beforeEach(async () => {
const module = {
app: {
apolloProvider: {
defaultClient: {
mutate: jest.fn(() => Promise.resolve(successfulLoginResponse))
}
},
$apolloHelpers: {
onLogin: jest.fn(() => Promise.resolve())
}
}
}
action = actions.login.bind(module)
await action(
{ commit, dispatch },
{ email: 'user@example.org', password: '1234' }
)
})
afterEach(() => {
action = null
})
it('saves the JWT Bearer token', () => {
expect(commit.mock.calls).toEqual(
expect.arrayContaining([['SET_TOKEN', token]])
)
})
it('fetches the user', () => {
expect(dispatch.mock.calls).toEqual([['fetchCurrentUser']])
})
it('saves pending flags in order', () => {
expect(commit.mock.calls).toEqual(