diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..ce4572cce --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v17.9.0 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index cb3b6a9a0..acc917e13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,27 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [1.0.7](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/1.0.6...1.0.7) + +- Bump rosie from 2.0.1 to 2.1.0 [`#4520`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4520) +- fix: 🍰 Renew JWT In Decode Test [`#4798`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4798) +- docs: 🍰 Refine Main README.md With Test Tech Stack And Video Link [`#4772`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4772) +- docs: 🍰 Change README.md DB Commands For Docker [`#4765`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4765) +- Bump date-fns from 2.23.0 to 2.25.0 [`#4753`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4753) +- Bump neo4j-driver from 4.0.2 to 4.3.4 [`#4729`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4729) +- chore: 🍰 Update Neode To v0.4.7 [`#4751`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4751) +- doc: 🍰 Update README.md Etc. [`#4733`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4733) +- feat: 🍰 New CSS For Internal Pages [`#4741`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4741) +- fix: 🍰 Change Notification E-Mails Settings Page Link [`#4742`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4742) +- Refactor internal pages to new CSS [`acad80c`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/acad80c3c8262934dd2e38961c08c0fde769099a) +- Renew JWT in decode test [`46eb6b8`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/46eb6b82ea802d4d6ca7294cd32d1fe16425bfea) +- Revert "Renew JWT in decode test" only for changing the Neode version [`a0d92b4`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/a0d92b4853d09d725c1fb7886cbfed2a00e1f05c) + #### [1.0.6](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/1.0.5...1.0.6) +> 4 October 2021 + +- chore: 🍰 Set 'sendNotification' Emails Init Admin [`#4690`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4690) - feat: 🍰 Send Notification E-Mail [`#4623`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4623) - feat: 🍰 Implement Progress Bar Again [`#4357`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4357) - Bump nodemailer-html-to-text from 3.1.0 to 3.2.0 in /backend [`#4531`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4531) diff --git a/README.md b/README.md index 603ea8178..45006581c 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,11 @@ At the same time, it should be possible in the future to link these networks wit In other words, we are interested in a network of networks and in keeping the data as close as possible to the user and the operator they trusts. +## Introduction + +Have a look into our short video: +[ocelot.social - GitHub - Developer Welcome - Tutorial (english)](https://www.youtube.com/watch?v=gZSL6KvBIiY&list=PLFMD5liPP01kbuReHxYXxv_1fI5rIgS1f&index=1) + ## Directory Layout There are three important directories: @@ -192,6 +197,14 @@ The only deployment method in this repository for development purposes as descri * [NodeJS](https://nodejs.org/en/) * [Neo4J](https://neo4j.com/) +### For Testing + +* [Cypress](https://docs.cypress.io/) +* [Storybook](https://storybook.js.org/) +* [Jest](https://jestjs.io/) +* [Vue Test Utils](https://vue-test-utils.vuejs.org/) +* [ESLint](https://eslint.org/) + ## Attributions Locale Icons made by [Freepik](http://www.freepik.com/) from [www.flaticon.com](https://www.flaticon.com/) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/). diff --git a/backend/.nvmrc b/backend/.nvmrc new file mode 100644 index 000000000..e4ab7d287 --- /dev/null +++ b/backend/.nvmrc @@ -0,0 +1 @@ +v12.19.0 \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index 6779c63ff..e0d53b5b9 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "ocelot-social-backend", - "version": "1.0.6", + "version": "1.0.7", "description": "GraphQL Backend for ocelot.social", "repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social", "author": "ocelot.social Community", @@ -66,7 +66,6 @@ "debug": "~4.1.1", "dotenv": "~8.2.0", "express": "^4.17.1", - "faker": "Marak/faker.js#master", "graphql": "^14.6.0", "graphql-custom-directives": "~0.2.14", "graphql-iso-date": "~3.6.1", @@ -120,6 +119,7 @@ "xregexp": "^4.3.0" }, "devDependencies": { + "@faker-js/faker": "5.1.0", "apollo-server-testing": "~2.11.0", "chai": "~4.2.0", "cucumber": "~6.0.5", diff --git a/backend/src/db/factories.js b/backend/src/db/factories.js index 6173238ae..3e164d51b 100644 --- a/backend/src/db/factories.js +++ b/backend/src/db/factories.js @@ -1,8 +1,8 @@ import { v4 as uuid } from 'uuid' -import faker from 'faker' import slugify from 'slug' import { hashSync } from 'bcryptjs' import { Factory } from 'rosie' +import faker from '@faker-js/faker' import { getDriver, getNeode } from './neo4j' import CONFIG from '../config/index.js' import generateInviteCode from '../schema/resolvers/helpers/generateInviteCode.js' diff --git a/backend/src/db/migrations/20210506150512-add-donations-node.js b/backend/src/db/migrations/20210506150512-add-donations-node.js index 699c451cf..6cbc1e897 100644 --- a/backend/src/db/migrations/20210506150512-add-donations-node.js +++ b/backend/src/db/migrations/20210506150512-add-donations-node.js @@ -19,8 +19,8 @@ export async function up(next) { SET donationInfo.createdAt = toString(datetime()) SET donationInfo.updatedAt = donationInfo.createdAt SET donationInfo.showDonations = false - SET donationInfo.goal = 15000 - SET donationInfo.progress = 1200 + SET donationInfo.goal = 15000.0 + SET donationInfo.progress = 1200.0 RETURN donationInfo {.*} `, { donationId }, diff --git a/backend/src/db/seed.js b/backend/src/db/seed.js index 7d9b5e954..786ad6fc8 100644 --- a/backend/src/db/seed.js +++ b/backend/src/db/seed.js @@ -1,7 +1,7 @@ -import faker from 'faker' import sample from 'lodash/sample' import { createTestClient } from 'apollo-server-testing' import createServer from '../server' +import faker from '@faker-js/faker' import Factory from '../db/factories' import { getNeode, getDriver } from '../db/neo4j' import { gql } from '../helpers/jest' diff --git a/backend/src/jwt/decode.spec.js b/backend/src/jwt/decode.spec.js index 78ceadecb..bb247f028 100644 --- a/backend/src/jwt/decode.spec.js +++ b/backend/src/jwt/decode.spec.js @@ -21,8 +21,14 @@ const neode = getNeode() // iss: 'http://localhost:4000', // sub: 'u3' // } +// !!! if the token expires go into the GraphQL Playground in the browser at 'http://localhost:4000' with a running backend and a seeded Neo4j database +// now do the login mutation: +// mutation { +// login(email:"user@example.org", password:"1234") +// } +// replace this token here with the one you received as the result export const validAuthorizationHeader = - 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidXNlciIsImxvY2F0aW9uTmFtZSI6bnVsbCwibmFtZSI6Ikplbm55IFJvc3RvY2siLCJhYm91dCI6bnVsbCwiYXZhdGFyIjoiaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3VpZmFjZXMvZmFjZXMvdHdpdHRlci9zYXNoYV9zaGVzdGFrb3YvMTI4LmpwZyIsImlkIjoidTMiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5vcmciLCJzbHVnIjoiamVubnktcm9zdG9jayIsImlhdCI6MTU1MDg0NjY4MCwiZXhwIjoxNjM3MjQ2NjgwLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQwMDAiLCJzdWIiOiJ1MyJ9.eZ_mVKas4Wzoc_JrQTEWXyRn7eY64cdIg4vqQ-F_7Jc' + 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InUzIiwibmFtZSI6Ikplbm55IFJvc3RvY2siLCJzbHVnIjoiamVubnktcm9zdG9jayIsImlhdCI6MTYzNzY0NDMwMCwiZXhwIjoxNzAwNzU5NTAwLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQwMDAiLCJzdWIiOiJ1MyJ9.ispIfRfgkXuYoIhKx7x2jPxgvHDJVv1ogMycLmfUnsk' beforeAll(async () => { await cleanDatabase() @@ -47,6 +53,7 @@ describe('decode', () => { beforeEach(() => { authorizationHeader = null }) + it('returns null', returnsNull) }) @@ -54,6 +61,7 @@ describe('decode', () => { beforeEach(() => { authorizationHeader = undefined }) + it('returns null', returnsNull) }) @@ -61,6 +69,7 @@ describe('decode', () => { beforeEach(() => { authorizationHeader = 'blah' }) + it('returns null', returnsNull) }) @@ -68,6 +77,7 @@ describe('decode', () => { beforeEach(() => { authorizationHeader = validAuthorizationHeader }) + it('returns null', returnsNull) describe('and corresponding user in the database', () => { diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js index 2bc53ab7c..5419771ea 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.js @@ -41,7 +41,6 @@ const publishNotifications = async (context, promises) => { notifications.forEach((notificationAdded, index) => { pubsub.publish(NOTIFICATION_ADDED, { notificationAdded }) if (notificationAdded.to.sendNotificationEmails) { - // Wolle await sendMail( notificationTemplate({ email: notificationsEmailAddresses[index].email, diff --git a/backend/src/schema/resolvers/users/location.spec.js b/backend/src/schema/resolvers/users/location.spec.js index e69b48f50..f150e8594 100644 --- a/backend/src/schema/resolvers/users/location.spec.js +++ b/backend/src/schema/resolvers/users/location.spec.js @@ -38,8 +38,8 @@ const newlyCreatedNodesWithLocales = [ nameRU: 'Вельцхайм', nameNL: 'Welzheim', namePL: 'Welzheim', - lng: 9.63444, - lat: 48.87472, + lng: 9.634741, + lat: 48.874924, }, state: { id: expect.stringContaining('region'), diff --git a/backend/yarn.lock b/backend/yarn.lock index 117383e58..24bd00b3a 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -963,6 +963,11 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@faker-js/faker@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-5.1.0.tgz#cee1d77ada0d0dbbe77201d18b1ebabf432d9c0f" + integrity sha512-0VonSKh7fBCqvY+V2FLN2ZW4pR4ZtWJalWmwSaiaB7yK7y4qp8vDfuaq9QdLjf/cdZGx3M7Wc4Q+x4fZHxI21Q== + "@graphql-toolkit/common@0.10.4": version "0.10.4" resolved "https://registry.yarnpkg.com/@graphql-toolkit/common/-/common-0.10.4.tgz#7785f2a3f14559d0778859c49f4442078c196695" @@ -4587,10 +4592,6 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -faker@Marak/faker.js#master: - version "4.1.0" - resolved "https://codeload.github.com/Marak/faker.js/tar.gz/3b2fa4aebccee52ae1bafc15d575061fb30c3cf1" - fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" diff --git a/cypress/integration/UserProfile.SocialMedia/I_add_a_social_media_link.js b/cypress/integration/UserProfile.SocialMedia/I_add_a_social_media_link.js index 9253709f9..ca2e36818 100644 --- a/cypress/integration/UserProfile.SocialMedia/I_add_a_social_media_link.js +++ b/cypress/integration/UserProfile.SocialMedia/I_add_a_social_media_link.js @@ -1,9 +1,12 @@ import { When } from "cypress-cucumber-preprocessor/steps"; When('I add a social media link', () => { - cy.get('input#addSocialMedia') - .type('https://freeradical.zone/peter-pan') - .get('button') + cy.get('button') .contains('Add link') .click() + .get('#editSocialMedia') + .type('https://freeradical.zone/peter-pan') + .get('button') + .contains('Save') + .click() }) \ No newline at end of file diff --git a/cypress/integration/UserProfile.SocialMedia/I_have_added_a_social_media_link.js b/cypress/integration/UserProfile.SocialMedia/I_have_added_a_social_media_link.js index 203b97032..93e35bf28 100644 --- a/cypress/integration/UserProfile.SocialMedia/I_have_added_a_social_media_link.js +++ b/cypress/integration/UserProfile.SocialMedia/I_have_added_a_social_media_link.js @@ -2,9 +2,12 @@ import { Given } from "cypress-cucumber-preprocessor/steps"; Given('I have added a social media link', () => { cy.visit('/settings/my-social-media') - .get('input#addSocialMedia') - .type('https://freeradical.zone/peter-pan') .get('button') .contains('Add link') .click() + .get('#editSocialMedia') + .type('https://freeradical.zone/peter-pan') + .get('button') + .contains('Save') + .click() }) \ No newline at end of file diff --git a/package.json b/package.json index 0fe64f55d..9b0e27d95 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ocelot-social", - "version": "1.0.6", + "version": "1.0.7", "description": "Free and open source software program code available to run social networks.", "author": "ocelot.social Community", "license": "MIT", @@ -36,7 +36,7 @@ "date-fns": "^2.25.0", "dotenv": "^8.2.0", "expect": "^25.3.0", - "faker": "Marak/faker.js#master", + "@faker-js/faker": "5.1.0", "graphql-request": "^2.0.0", "import": "^0.0.6", "jsonwebtoken": "^8.5.1", @@ -44,7 +44,7 @@ "neo4j-driver": "^4.3.4", "neode": "^0.4.7", "npm-run-all": "^4.1.5", - "rosie": "^2.0.1", + "rosie": "^2.1.0", "slug": "^5.1.0" }, "resolutions": { diff --git a/webapp/.nvmrc b/webapp/.nvmrc new file mode 100644 index 000000000..e4ab7d287 --- /dev/null +++ b/webapp/.nvmrc @@ -0,0 +1 @@ +v12.19.0 \ No newline at end of file diff --git a/webapp/components/CommentList/CommentList.story.js b/webapp/components/CommentList/CommentList.story.js index 3ef474c80..50a2b1d1b 100644 --- a/webapp/components/CommentList/CommentList.story.js +++ b/webapp/components/CommentList/CommentList.story.js @@ -1,8 +1,8 @@ +import faker from '@faker-js/faker' import { storiesOf } from '@storybook/vue' import { withA11y } from '@storybook/addon-a11y' import HcCommentList from './CommentList.vue' import helpers from '~/storybook/helpers' -import faker from 'faker' helpers.init() diff --git a/webapp/components/Editor/MenuLegend.vue b/webapp/components/Editor/MenuLegend.vue index afc137b1f..2d897eeb5 100644 --- a/webapp/components/Editor/MenuLegend.vue +++ b/webapp/components/Editor/MenuLegend.vue @@ -3,12 +3,16 @@ @@ -60,6 +64,7 @@ export default { { iconName: 'quote-right', name: `editor.legend.quote`, shortcut: '> + space' }, { iconName: 'minus', name: `editor.legend.ruler`, shortcut: '---' }, ], + isDropdownOpen: false, } }, } diff --git a/webapp/components/Embed/EmbedComponent.vue b/webapp/components/Embed/EmbedComponent.vue index 6161f8f70..af9520538 100644 --- a/webapp/components/Embed/EmbedComponent.vue +++ b/webapp/components/Embed/EmbedComponent.vue @@ -179,10 +179,24 @@ export default { } .html { - width: 100%; + // width: 100%; + // height: 100%; + + // see this working solution here: https://stackoverflow.com/questions/35814653/automatic-height-when-embedding-a-youtube-video + position: relative; + padding-bottom: 56.25%; /* 16:9 */ + height: 0; iframe { + // width: 100%; + // height: auto; + + // same solution example as above + position: absolute; + top: 0; + left: 0; width: 100%; + height: 100%; } } diff --git a/webapp/components/_new/features/MySomethingList/MySomethingList.spec.js b/webapp/components/_new/features/MySomethingList/MySomethingList.spec.js new file mode 100644 index 000000000..513207110 --- /dev/null +++ b/webapp/components/_new/features/MySomethingList/MySomethingList.spec.js @@ -0,0 +1,128 @@ +import { mount } from '@vue/test-utils' +import MySomethingList from './MySomethingList.vue' +import Vue from 'vue' + +const localVue = global.localVue + +describe('MySomethingList.vue', () => { + let wrapper + let propsData + let data + let mocks + + beforeEach(() => { + propsData = { + useFormData: { dummy: '' }, + useItems: [{ id: 'id', dummy: 'dummy' }], + namePropertyKey: 'dummy', + callbacks: { edit: jest.fn(), submit: jest.fn(), delete: jest.fn() }, + } + data = () => { + return {} + } + mocks = { + $t: jest.fn(), + $apollo: { + mutate: jest.fn(), + }, + $toast: { + error: jest.fn(), + success: jest.fn(), + }, + } + }) + + describe('mount', () => { + let form, slots + const Wrapper = () => { + slots = { + 'list-item': '
', + 'edit-item': '
', + } + return mount(MySomethingList, { + propsData, + data, + mocks, + localVue, + slots, + }) + } + + describe('given existing item', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + describe('for each item it', () => { + it('displays the item as slot "list-item"', () => { + expect(wrapper.find('.list-item').exists()).toBe(true) + }) + + it('displays the edit button', () => { + expect(wrapper.find('.base-button[data-test="edit-button"]').exists()).toBe(true) + }) + + it('displays the delete button', () => { + expect(wrapper.find('.base-button[data-test="delete-button"]').exists()).toBe(true) + }) + }) + + describe('editing item', () => { + beforeEach(async () => { + const editButton = wrapper.find('.base-button[data-test="edit-button"]') + editButton.trigger('click') + await Vue.nextTick() + }) + + it('disables adding items while editing', () => { + const submitButton = wrapper.find('.base-button[data-test="add-save-button"]') + expect(submitButton.text()).not.toContain('settings.social-media.submit') + }) + + it('allows the user to cancel editing', async () => { + expect(wrapper.find('.edit-item').exists()).toBe(true) + const cancelButton = wrapper.find('button#cancel') + cancelButton.trigger('click') + await Vue.nextTick() + expect(wrapper.find('.edit-item').exists()).toBe(false) + }) + }) + + describe('calls callback functions', () => { + it('calls edit', async () => { + const editButton = wrapper.find('.base-button[data-test="edit-button"]') + editButton.trigger('click') + await Vue.nextTick() + const expectedItem = expect.objectContaining({ id: 'id', dummy: 'dummy' }) + expect(propsData.callbacks.edit).toHaveBeenCalledTimes(1) + expect(propsData.callbacks.edit).toHaveBeenCalledWith(expect.any(Object), expectedItem) + }) + + it('calls submit', async () => { + form = wrapper.find('form') + form.trigger('submit') + await Vue.nextTick() + form.trigger('submit') + await Vue.nextTick() + const expectedItem = expect.objectContaining({ id: '' }) + expect(propsData.callbacks.submit).toHaveBeenCalledTimes(1) + expect(propsData.callbacks.submit).toHaveBeenCalledWith( + expect.any(Object), + true, + expectedItem, + { dummy: '' }, + ) + }) + + it('calls delete', async () => { + const deleteButton = wrapper.find('.base-button[data-test="delete-button"]') + deleteButton.trigger('click') + await Vue.nextTick() + const expectedItem = expect.objectContaining({ id: 'id', dummy: 'dummy' }) + expect(propsData.callbacks.delete).toHaveBeenCalledTimes(1) + expect(propsData.callbacks.delete).toHaveBeenCalledWith(expect.any(Object), expectedItem) + }) + }) + }) + }) +}) diff --git a/webapp/components/_new/features/MySomethingList/MySomethingList.vue b/webapp/components/_new/features/MySomethingList/MySomethingList.vue new file mode 100644 index 000000000..fbfcca932 --- /dev/null +++ b/webapp/components/_new/features/MySomethingList/MySomethingList.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/webapp/components/_new/features/SocialMedia/SocialMediaListItem.spec.js b/webapp/components/_new/features/SocialMedia/SocialMediaListItem.spec.js new file mode 100644 index 000000000..c7359d873 --- /dev/null +++ b/webapp/components/_new/features/SocialMedia/SocialMediaListItem.spec.js @@ -0,0 +1,36 @@ +import { shallowMount } from '@vue/test-utils' +import SocialMediaListItem from './SocialMediaListItem.vue' + +describe('SocialMediaListItem.vue', () => { + let wrapper + let propsData + const socialMediaUrl = 'https://freeradical.zone/@mattwr18' + const faviconUrl = 'https://freeradical.zone/favicon.ico' + + beforeEach(() => { + propsData = {} + }) + + describe('shallowMount', () => { + const Wrapper = () => { + return shallowMount(SocialMediaListItem, { propsData }) + } + + describe('given existing social media links', () => { + beforeEach(() => { + propsData = { item: { id: 's1', url: socialMediaUrl, favicon: faviconUrl } } + wrapper = Wrapper() + }) + + describe('for each link item it', () => { + it('displays the favicon', () => { + expect(wrapper.find(`img[src="${faviconUrl}"]`).exists()).toBe(true) + }) + + it('displays the url', () => { + expect(wrapper.find(`a[href="${socialMediaUrl}"]`).exists()).toBe(true) + }) + }) + }) + }) +}) diff --git a/webapp/components/_new/features/SocialMedia/SocialMediaListItem.vue b/webapp/components/_new/features/SocialMedia/SocialMediaListItem.vue new file mode 100644 index 000000000..75dea7ba8 --- /dev/null +++ b/webapp/components/_new/features/SocialMedia/SocialMediaListItem.vue @@ -0,0 +1,18 @@ + + + diff --git a/webapp/components/_new/generic/TabNavigation/TabNavigator.story.js b/webapp/components/_new/generic/TabNavigation/TabNavigator.story.js index 457e78209..6da7a5763 100644 --- a/webapp/components/_new/generic/TabNavigation/TabNavigator.story.js +++ b/webapp/components/_new/generic/TabNavigation/TabNavigator.story.js @@ -1,3 +1,4 @@ +import faker from '@faker-js/faker' import { storiesOf } from '@storybook/vue' import { withA11y } from '@storybook/addon-a11y' import HcEmpty from '~/components/Empty/Empty' @@ -8,7 +9,6 @@ import TabNavigation from '~/components/_new/generic/TabNavigation/TabNavigation import UserTeaser from '~/components/UserTeaser/UserTeaser' import HcHashtag from '~/components/Hashtag/Hashtag' import helpers from '~/storybook/helpers' -import faker from 'faker' import { post } from '~/components/PostTeaser/PostTeaser.story.js' import { user } from '~/components/UserTeaser/UserTeaser.story.js' diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 9816e167a..a98f0a320 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -773,6 +773,8 @@ "name": "Sicherheit" }, "social-media": { + "addNewTitle": "Neuen Link hinzufügen", + "editTitle": "Link \"{name}\" ändern", "name": "Soziale Netzwerke", "placeholder": "Deine Webadresse des Sozialen Netzwerkes", "requireUnique": "Dieser Link existiert bereits", diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 11b920056..f6606b933 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -773,6 +773,8 @@ "name": "Security" }, "social-media": { + "addNewTitle": "Add new link", + "editTitle": "Edit link \"{name}\"", "name": "Social media", "placeholder": "Your social media url", "requireUnique": "You added this url already", diff --git a/webapp/maintenance/source/package.json b/webapp/maintenance/source/package.json index 2391b5c91..e38f34370 100644 --- a/webapp/maintenance/source/package.json +++ b/webapp/maintenance/source/package.json @@ -1,6 +1,6 @@ { "name": "@ocelot-social/maintenance", - "version": "1.0.6", + "version": "1.0.7", "description": "Maintenance page for ocelot.social", "repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social", "author": "ocelot.social Community", diff --git a/webapp/nuxt.config.js b/webapp/nuxt.config.js index b4fc3aa7c..717aa0339 100644 --- a/webapp/nuxt.config.js +++ b/webapp/nuxt.config.js @@ -201,6 +201,7 @@ export default { /** * A Boolean indicating if the cookie transmission requires a * secure protocol (https). Defaults to false. */ secure: CONFIG.COOKIE_HTTPS_ONLY, + sameSite: 'lax', // for the meaning see https://www.thinktecture.com/de/identity/samesite/samesite-in-a-nutshell/ }, // includeNodeModules: true, // optional, default: false (this includes graphql-tag for node_modules folder) diff --git a/webapp/package.json b/webapp/package.json index 0df821b44..cf4354dde 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -1,6 +1,6 @@ { "name": "ocelot-social-webapp", - "version": "1.0.6", + "version": "1.0.7", "description": "ocelot.social Frontend", "repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social", "author": "ocelot.social Community", @@ -108,6 +108,7 @@ "@babel/core": "~7.12.3", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/preset-env": "~7.9.0", + "@faker-js/faker": "5.1.0", "@storybook/addon-a11y": "^6.3.6", "@storybook/addon-actions": "^5.3.21", "@storybook/addon-notes": "^5.3.18", @@ -136,7 +137,6 @@ "eslint-plugin-promise": "~4.3.1", "eslint-plugin-standard": "~5.0.0", "eslint-plugin-vue": "~6.2.2", - "faker": "^5.1.0", "flush-promises": "^1.0.2", "identity-obj-proxy": "^3.0.0", "jest": "~26.6.3", diff --git a/webapp/pages/settings/my-social-media.spec.js b/webapp/pages/settings/my-social-media.spec.js index 1aadbb750..7136e1e7c 100644 --- a/webapp/pages/settings/my-social-media.spec.js +++ b/webapp/pages/settings/my-social-media.spec.js @@ -33,7 +33,7 @@ describe('my-social-media.vue', () => { }) describe('mount', () => { - let form, input, submitButton + let form, input const Wrapper = () => { const store = new Vuex.Store({ getters, @@ -42,11 +42,12 @@ describe('my-social-media.vue', () => { } describe('adding social media link', () => { - beforeEach(() => { + beforeEach(async () => { wrapper = Wrapper() form = wrapper.find('form') - input = wrapper.find('input#addSocialMedia') - submitButton = wrapper.find('button') + form.trigger('submit') + await Vue.nextTick() + input = wrapper.find('input#editSocialMedia') }) it('requires the link to be a valid url', async () => { @@ -79,7 +80,6 @@ describe('my-social-media.vue', () => { const expected = expect.objectContaining({ variables: { url: newSocialMediaUrl }, }) - expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected) }) @@ -88,10 +88,10 @@ describe('my-social-media.vue', () => { expect(mocks.$toast.success).toHaveBeenCalledTimes(1) }) - it('clears the form', async () => { + it('switches back to list', async () => { await flushPromises() - expect(input.value).toBe(undefined) - expect(submitButton.vm.$attrs.disabled).toBe(true) + const submitButton = wrapper.find('.base-button[data-test="add-save-button"]') + expect(submitButton.text()).not.toContain('settings.social-media.submit') }) }) }) @@ -100,10 +100,9 @@ describe('my-social-media.vue', () => { beforeEach(() => { getters = { 'auth/user': () => ({ - socialMedia: [{ id: 's1', url: socialMediaUrl }], + socialMedia: [{ id: 's1', url: socialMediaUrl, favicon: faviconUrl }], }), } - wrapper = Wrapper() form = wrapper.find('form') }) @@ -116,18 +115,12 @@ describe('my-social-media.vue', () => { it('displays the url', () => { expect(wrapper.find(`a[href="${socialMediaUrl}"]`).exists()).toBe(true) }) - - it('displays the edit button', () => { - expect(wrapper.find('.base-button[data-test="edit-button"]').exists()).toBe(true) - }) - - it('displays the delete button', () => { - expect(wrapper.find('.base-button[data-test="delete-button"]').exists()).toBe(true) - }) }) it('does not accept a duplicate url', async () => { - wrapper.find('input#addSocialMedia').setValue(socialMediaUrl) + form.trigger('submit') + await Vue.nextTick() + wrapper.find('input#editSocialMedia').setValue(socialMediaUrl) form.trigger('submit') await Vue.nextTick() expect(mocks.$apollo.mutate).not.toHaveBeenCalled() @@ -141,12 +134,6 @@ describe('my-social-media.vue', () => { input = wrapper.find('input#editSocialMedia') }) - it('disables adding new links while editing', () => { - const addInput = wrapper.find('input#addSocialMedia') - - expect(addInput.exists()).toBe(false) - }) - it('sends the new url to the backend', async () => { const expected = expect.objectContaining({ variables: { id: 's1', url: newSocialMediaUrl }, @@ -156,13 +143,6 @@ describe('my-social-media.vue', () => { await Vue.nextTick() expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected) }) - - it('allows the user to cancel editing', async () => { - const cancelButton = wrapper.find('button#cancel') - cancelButton.trigger('click') - await Vue.nextTick() - expect(wrapper.find('input#editSocialMedia').exists()).toBe(false) - }) }) describe('deleting social media link', () => { @@ -176,7 +156,6 @@ describe('my-social-media.vue', () => { const expected = expect.objectContaining({ variables: { id: 's1' }, }) - expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1) expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected) }) diff --git a/webapp/pages/settings/my-social-media.vue b/webapp/pages/settings/my-social-media.vue index 5753fc405..a985371d6 100644 --- a/webapp/pages/settings/my-social-media.vue +++ b/webapp/pages/settings/my-social-media.vue @@ -1,97 +1,73 @@ - - diff --git a/webapp/plugins/i18n.js b/webapp/plugins/i18n.js index 8c64ef0c3..f162408f6 100644 --- a/webapp/plugins/i18n.js +++ b/webapp/plugins/i18n.js @@ -21,18 +21,30 @@ export default ({ app, req, cookie, store }) => { const changeHandler = async (mutation) => { if (process.server) return - const newLocale = mutation.payload.locale - const currentLocale = await app.$cookies.get(key) - const isDifferent = newLocale !== currentLocale + const localeInStore = mutation.payload.locale + let cookieExists = true + let localeInCookies = await app.$cookies.get(key) + if (!localeInCookies) { + cookieExists = false + localeInCookies = navigator.language.split('-')[0] // get browser language + } + const isLocaleStoreSameAsCookies = localeInStore === localeInCookies - if (!isDifferent) { + // cookie has to be set, otherwise Cypress test does not work + if (cookieExists && isLocaleStoreSameAsCookies) { return } - app.$cookies.set(key, newLocale) - if (!app.$i18n.localeExists(newLocale)) { - import(`~/locales/${newLocale}.json`).then((res) => { - app.$i18n.add(newLocale, res.default) + const expires = new Date() + expires.setDate(expires.getDate() + app.$env.COOKIE_EXPIRE_TIME) + app.$cookies.set(key, localeInStore, { + expires, + // maxAge: app.$env.COOKIE_EXPIRE_TIME * 60 * 60 * 24, // days to seconds + sameSite: 'lax', // for the meaning see https://www.thinktecture.com/de/identity/samesite/samesite-in-a-nutshell/ + }) + if (!app.$i18n.localeExists(localeInStore)) { + import(`~/locales/${localeInStore}.json`).then((res) => { + app.$i18n.add(localeInStore, res.default) }) } @@ -42,7 +54,7 @@ export default ({ app, req, cookie, store }) => { if (user && user._id && token) { // TODO: SAVE LOCALE // store.dispatch('usersettings/patch', { - // uiLanguage: newLocale + // uiLanguage: localeInStore // }, { root: true }) } } diff --git a/webapp/storybook/helpers.js b/webapp/storybook/helpers.js index 5fcd8e64b..e53e1d554 100644 --- a/webapp/storybook/helpers.js +++ b/webapp/storybook/helpers.js @@ -1,14 +1,13 @@ import Vue from 'vue' import Vuex from 'vuex' -import faker from 'faker' import vuexI18n from 'vuex-i18n/dist/vuex-i18n.umd.js' import Styleguide from '@human-connection/styleguide' +import faker from '@faker-js/faker' import Filters from '~/plugins/vue-filters' import Directives from '~/plugins/vue-directives' import IziToast from '~/plugins/izi-toast' import layout from './layout.vue' import locales from '~/locales/index.js' - import '~/plugins/v-tooltip' const helpers = { diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 9ff5a19b7..ef71a2a53 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -2379,6 +2379,11 @@ ts-node "^8" tslib "^1" +"@faker-js/faker@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-5.1.0.tgz#cee1d77ada0d0dbbe77201d18b1ebabf432d9c0f" + integrity sha512-0VonSKh7fBCqvY+V2FLN2ZW4pR4ZtWJalWmwSaiaB7yK7y4qp8vDfuaq9QdLjf/cdZGx3M7Wc4Q+x4fZHxI21Q== + "@hapi/address@2.x.x": version "2.0.0" resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.0.0.tgz#9f05469c88cb2fd3dcd624776b54ee95c312126a" @@ -10235,11 +10240,6 @@ fake-tag@^1.0.0: resolved "https://registry.yarnpkg.com/fake-tag/-/fake-tag-1.0.1.tgz#1d59da482240a02bd83500ca98976530ed154b0d" integrity sha512-qmewZoBpa71mM+y6oxXYW/d1xOYQmeIvnEXAt1oCmdP0sqcogWYLepR87QL1jQVLSVMVYDq2cjY6ec/Wu8/4pg== -faker@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/faker/-/faker-5.1.0.tgz#e10fa1dec4502551aee0eb771617a7e7b94692e8" - integrity sha512-RrWKFSSA/aNLP0g3o2WW1Zez7/MnMr7xkiZmoCfAGZmdkDQZ6l2KtuXHN5XjdvpRjDl8+3vf+Rrtl06Z352+Mw== - fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" diff --git a/yarn.lock b/yarn.lock index 01666ca81..4e2303801 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1248,6 +1248,11 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@faker-js/faker@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-5.1.0.tgz#cee1d77ada0d0dbbe77201d18b1ebabf432d9c0f" + integrity sha512-0VonSKh7fBCqvY+V2FLN2ZW4pR4ZtWJalWmwSaiaB7yK7y4qp8vDfuaq9QdLjf/cdZGx3M7Wc4Q+x4fZHxI21Q== + "@hapi/address@2.x.x": version "2.0.0" resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.0.0.tgz#9f05469c88cb2fd3dcd624776b54ee95c312126a" @@ -3058,10 +3063,6 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -faker@Marak/faker.js#master: - version "5.1.0" - resolved "https://codeload.github.com/Marak/faker.js/tar.gz/91dc8a3372426bc691be56153b33e81a16459f49" - fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" @@ -5274,10 +5275,10 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -rosie@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/rosie/-/rosie-2.0.1.tgz#c250c4787ce450b72aa9eff26509f68589814fa2" - integrity sha1-wlDEeHzkULcqqe/yZQn2hYmBT6I= +rosie@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/rosie/-/rosie-2.1.0.tgz#0213a9d2b0401a2549cbce5f1cd914caffa22358" + integrity sha512-Dbzdc+prLXZuB/suRptDnBUY29SdGvND3bLg6cll8n7PNqzuyCxSlRfrkn8PqjS9n4QVsiM7RCvxCkKAkTQRjA== rxjs@^6.3.3, rxjs@^6.6.3: version "6.6.7"