diff --git a/frontend/package.json b/frontend/package.json index 3dde845f4..d45c29e34 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "@babel/preset-env": "^7.13.12", "@vue/cli-plugin-unit-jest": "^4.5.12", "@vue/test-utils": "^1.1.3", + "apexcharts": "^3.28.3", "apollo-boost": "^0.4.9", "axios": "^0.21.1", "babel-core": "^7.0.0-bridge.0", @@ -61,6 +62,7 @@ "sweetalert2": "^9.5.4", "vee-validate": "^3.4.5", "vue": "^2.6.11", + "vue-apexcharts": "^1.6.2", "vue-apollo": "^3.0.7", "vue-bootstrap-typeahead": "^0.2.6", "vue-cli-plugin-i18n": "^1.0.1", diff --git a/frontend/src/components/charts/ChartExample.vue b/frontend/src/components/charts/ChartExample.vue new file mode 100644 index 000000000..7c740f512 --- /dev/null +++ b/frontend/src/components/charts/ChartExample.vue @@ -0,0 +1,112 @@ + + + diff --git a/frontend/src/components/charts/series.js b/frontend/src/components/charts/series.js new file mode 100644 index 000000000..759a2d401 --- /dev/null +++ b/frontend/src/components/charts/series.js @@ -0,0 +1,224 @@ +export const series = [ + { + name: 'obb.docker', + data: [ + { + x: '2018-09-25', + y: 900, + }, + { + x: '2018-09-26', + y: 900, + }, + { + x: '2018-09-27', + y: 900, + }, + { + x: '2018-09-28', + y: 900, + }, + { + x: '2018-09-29', + y: 900, + }, + { + x: '2018-09-30', + y: 900, + }, + { + x: '2018-10-01', + y: 900, + }, + { + x: '2018-10-02', + y: 1125, + }, + ], + }, + { + name: 'filato.dk', + data: [ + { + x: '2018-09-25', + y: 1251.45, + }, + { + x: '2018-09-26', + y: 984.94, + }, + { + x: '2018-09-27', + y: 1170.34, + }, + { + x: '2018-09-28', + y: 1193.51, + }, + { + x: '2018-09-29', + y: 1251.45, + }, + { + x: '2018-09-30', + y: 1147.16, + }, + { + x: '2018-10-01', + y: 1263.04, + }, + { + x: '2018-10-02', + y: 1158.75, + }, + ], + }, + { + name: 'rito.dk', + data: [ + { + x: '2018-09-25', + y: 1193.06, + }, + { + x: '2018-09-26', + y: 1193.06, + }, + { + x: '2018-09-27', + y: 1068.08, + }, + { + x: '2018-09-28', + y: 909, + }, + { + x: '2018-09-29', + y: 965.81, + }, + { + x: '2018-09-30', + y: 965.81, + }, + { + x: '2018-10-01', + y: 1022.63, + }, + { + x: '2018-10-02', + y: 1136.25, + }, + ], + }, + { + name: 'fruhyasinth.dk', + data: [ + { + x: '2018-09-25', + y: 1064.7, + }, + { + x: '2018-09-26', + y: 1146.6, + }, + { + x: '2018-09-27', + y: 1216.8, + }, + { + x: '2018-09-28', + y: 959.4, + }, + { + x: '2018-09-29', + y: 1193.4, + }, + { + x: '2018-09-30', + y: 1017.9, + }, + { + x: '2018-10-01', + y: 1275.3, + }, + { + x: '2018-10-02', + y: 1170, + }, + ], + }, + { + name: 'bilka.dk', + data: [ + { + x: '2018-09-25', + y: 973.35, + }, + { + x: '2018-09-26', + y: 1147.16, + }, + { + x: '2018-09-27', + y: 1263.04, + }, + { + x: '2018-09-28', + y: 927, + }, + { + x: '2018-09-29', + y: 950.18, + }, + { + x: '2018-09-30', + y: 1123.99, + }, + { + x: '2018-10-01', + y: 1158.75, + }, + { + x: '2018-10-02', + y: 1158.75, + }, + ], + }, + { + name: 'hobbii.dk', + data: [ + { + x: '2018-09-25', + y: 963.9, + }, + { + x: '2018-09-26', + y: 1136.03, + }, + { + x: '2018-09-27', + y: 1216.35, + }, + { + x: '2018-09-28', + y: 1067.18, + }, + { + x: '2018-09-29', + y: 952.43, + }, + { + x: '2018-09-30', + y: 929.48, + }, + { + x: '2018-10-01', + y: 1204.88, + }, + { + x: '2018-10-02', + y: 1147.5, + }, + ], + }, +] diff --git a/frontend/src/i18n.js b/frontend/src/i18n.js index 98e4ec971..946507c82 100644 --- a/frontend/src/i18n.js +++ b/frontend/src/i18n.js @@ -4,6 +4,9 @@ import VueI18n from 'vue-i18n' import en from 'vee-validate/dist/locale/en' import de from 'vee-validate/dist/locale/de' +import enCharts from 'apexcharts/dist/locales/en' +import deCharts from 'apexcharts/dist/locales/de' + Vue.use(VueI18n) function loadLocaleMessages() { @@ -17,12 +20,14 @@ function loadLocaleMessages() { if (locale === 'de') { messages[locale] = { validations: de, + charts: deCharts, ...messages[locale], } } if (locale === 'en') { messages[locale] = { validations: en, + charts: enCharts, ...messages[locale], } } diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index ad0cf9335..93b70318c 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -102,8 +102,8 @@ "logout": "Abmelden", "members_area": "Mitgliederbereich", "message": "hallo gradido !!", - "privacy_policy": "Datenschutzerklärung", "overview": "Übersicht", + "privacy_policy": "Datenschutzerklärung", "send": "Senden", "settings": { "coinanimation": { diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 454f34dbd..d082a7133 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -102,8 +102,8 @@ "logout": "Logout", "members_area": "Member's area", "message": "hello gradido !!", - "privacy_policy": "Privacy policy", "overview": "Overview", + "privacy_policy": "Privacy policy", "send": "Send", "settings": { "coinanimation": { diff --git a/frontend/src/main.js b/frontend/src/main.js index f71441924..5ca40591e 100755 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -6,7 +6,7 @@ import { loadAllRules } from './validation-rules' import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost' import VueApollo from 'vue-apollo' import CONFIG from './config' -import VueCharts from 'vue-chartjs' +import VueApexCharts from 'vue-apexcharts' import { store } from './store/store' import router from './routes/router' @@ -57,6 +57,9 @@ router.beforeEach((to, from, next) => { } }) +Vue.use(VueApexCharts) +Vue.component('apexchart', VueApexCharts) + /* eslint-disable no-new */ new Vue({ el: '#app', @@ -64,6 +67,5 @@ new Vue({ store, i18n, apolloProvider, - VueCharts, render: (h) => h(App), }) diff --git a/frontend/src/views/Layout/DashboardLayout_gdd.spec.js b/frontend/src/views/Layout/DashboardLayout_gdd.spec.js index 6555d1dda..d737407dd 100644 --- a/frontend/src/views/Layout/DashboardLayout_gdd.spec.js +++ b/frontend/src/views/Layout/DashboardLayout_gdd.spec.js @@ -84,33 +84,43 @@ describe('DashboardLayoutGdd', () => { navbar = wrapper.findAll('ul.navbar-nav').at(0) }) - it('has three items in the navbar', () => { - expect(navbar.findAll('ul > a')).toHaveLength(3) + it('has four items in the navbar', () => { + expect(navbar.findAll('ul > a')).toHaveLength(4) }) - it('has first item "send" in navbar', () => { - expect(navbar.findAll('ul > a').at(0).text()).toEqual('send') + it('has first item "overview" in navbar', () => { + expect(navbar.findAll('ul > a').at(0).text()).toEqual('overview') }) - it('has first item "send" linked to overview in navbar', () => { + it('has first item "overview" linked to overview in navbar', () => { navbar.findAll('ul > a').at(0).trigger('click') - expect(wrapper.findComponent(RouterLinkStub).props().to).toBe('/overview') + expect(wrapper.findComponent(RouterLinkStub).props(0).to).toBe('/overview') }) - it('has second item "transactions" in navbar', () => { - expect(navbar.findAll('ul > a').at(1).text()).toEqual('transactions') + it('has second item "send" in navbar', () => { + expect(navbar.findAll('ul > a').at(1).text()).toEqual('send') }) - it('has second item "transactions" linked to transactions in navbar', async () => { - expect(wrapper.findAll('a').at(3).attributes('href')).toBe('/transactions') + it('has second item "send" linked to sendoverview in navbar', () => { + navbar.findAll('ul > a').at(1).trigger('click') + expect(wrapper.findAll('a').at(3).attributes('href')).toBe('/send') }) - it('has three items in the navbar', () => { - expect(navbar.findAll('ul > a')).toHaveLength(3) + it('has three item "transactions" in navbar', () => { + expect(navbar.findAll('ul > a').at(2).text()).toEqual('transactions') + }) + + it('has three item "transactions" linked to transactions in navbar', async () => { + navbar.findAll('ul > a').at(2).trigger('click') + expect(wrapper.findAll('a').at(5).attributes('href')).toBe('/transactions') + }) + + it('has three item "profil" in navbar', () => { + expect(navbar.findAll('ul > a').at(3).text()).toEqual('site.navbar.my-profil') }) it('has third item "My profile" linked to profile in navbar', async () => { - expect(wrapper.findAll('a').at(5).attributes('href')).toBe('/profile') + expect(wrapper.findAll('a').at(7).attributes('href')).toBe('/profile') }) it('has a link to the members area', () => { diff --git a/frontend/src/views/Pages/AccountOverview.spec.js b/frontend/src/views/Pages/AccountOverview.spec.js index 9d876f3ac..0ea413fa7 100644 --- a/frontend/src/views/Pages/AccountOverview.spec.js +++ b/frontend/src/views/Pages/AccountOverview.spec.js @@ -1,9 +1,6 @@ import { mount } from '@vue/test-utils' import AccountOverview from './AccountOverview' -const sendMock = jest.fn() -sendMock.mockResolvedValue('success') - const localVue = global.localVue window.scrollTo = jest.fn() @@ -11,26 +8,17 @@ window.scrollTo = jest.fn() describe('AccountOverview', () => { let wrapper - const propsData = { - balance: 123.45, - transactionCount: 1, - } - const mocks = { $t: jest.fn((t) => t), - $n: jest.fn((n) => String(n)), - $store: { - state: { - email: 'sender@example.org', - }, - }, - $apollo: { - mutate: sendMock, - }, + $n: jest.fn(), } const Wrapper = () => { - return mount(AccountOverview, { localVue, mocks, propsData }) + return mount(AccountOverview, { + localVue, + mocks, + stubs: ['apexchart'], + }) } describe('mount', () => { @@ -42,94 +30,8 @@ describe('AccountOverview', () => { expect(wrapper.find('div.gdd-status').exists()).toBeTruthy() }) - it('has a send field', () => { - expect(wrapper.find('div.gdd-send').exists()).toBeTruthy() - }) - it('has a transactions table', () => { expect(wrapper.find('div.gdd-transaction-list').exists()).toBeTruthy() }) - - describe('transaction form', () => { - it('steps forward in the dialog', async () => { - await wrapper.findComponent({ name: 'TransactionForm' }).vm.$emit('set-transaction', { - email: 'user@example.org', - amount: 23.45, - memo: 'Make the best of it!', - }) - expect(wrapper.findComponent({ name: 'TransactionConfirmation' }).exists()).toBeTruthy() - }) - }) - - describe('confirm transaction', () => { - beforeEach(() => { - wrapper.setData({ - currentTransactionStep: 1, - transactionData: { - email: 'user@example.org', - amount: 23.45, - memo: 'Make the best of it!', - }, - }) - }) - - it('resets the transaction process when on-reset is emitted', async () => { - await wrapper.findComponent({ name: 'TransactionConfirmation' }).vm.$emit('on-reset') - expect(wrapper.findComponent({ name: 'TransactionForm' }).exists()).toBeTruthy() - expect(wrapper.vm.transactionData).toEqual({ - email: '', - amount: 0, - memo: '', - }) - }) - - describe('transaction is confirmed and server response is success', () => { - beforeEach(async () => { - jest.clearAllMocks() - await wrapper - .findComponent({ name: 'TransactionConfirmation' }) - .vm.$emit('send-transaction') - }) - - it('calls the API when send-transaction is emitted', async () => { - expect(sendMock).toBeCalledWith( - expect.objectContaining({ - variables: { - email: 'user@example.org', - amount: 23.45, - memo: 'Make the best of it!', - }, - }), - ) - }) - - it('emits update-balance', () => { - expect(wrapper.emitted('update-balance')).toBeTruthy() - expect(wrapper.emitted('update-balance')).toEqual([[23.45]]) - }) - - it('shows the succes page', () => { - expect(wrapper.find('div.card-body').text()).toContain('form.send_transaction_success') - }) - }) - - describe('transaction is confirmed and server response is error', () => { - beforeEach(async () => { - jest.clearAllMocks() - sendMock.mockRejectedValue({ message: 'receiver not found' }) - await wrapper - .findComponent({ name: 'TransactionConfirmation' }) - .vm.$emit('send-transaction') - }) - - it('shows the error page', () => { - expect(wrapper.find('div.card-body').text()).toContain('form.send_transaction_error') - }) - - it('shows recipient not found', () => { - expect(wrapper.text()).toContain('transaction.receiverNotFound') - }) - }) - }) }) }) diff --git a/frontend/src/views/Pages/AccountOverview.vue b/frontend/src/views/Pages/AccountOverview.vue index 41d75a27c..1064fedaf 100644 --- a/frontend/src/views/Pages/AccountOverview.vue +++ b/frontend/src/views/Pages/AccountOverview.vue @@ -1,29 +1,56 @@ diff --git a/frontend/src/views/Pages/AccountOverview/GddStatus.vue b/frontend/src/views/Pages/AccountOverview/GddStatus.vue deleted file mode 100644 index 4ab675485..000000000 --- a/frontend/src/views/Pages/AccountOverview/GddStatus.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/frontend/src/views/Pages/AccountOverview/GddStatus.spec.js b/frontend/src/views/Pages/GddGdtStatus.spec.js similarity index 70% rename from frontend/src/views/Pages/AccountOverview/GddStatus.spec.js rename to frontend/src/views/Pages/GddGdtStatus.spec.js index b414f4e93..02b8d8574 100644 --- a/frontend/src/views/Pages/AccountOverview/GddStatus.spec.js +++ b/frontend/src/views/Pages/GddGdtStatus.spec.js @@ -1,5 +1,5 @@ import { mount } from '@vue/test-utils' -import GddStatus from './GddStatus' +import GddStatus from './GddGdtStatus' const localVue = global.localVue @@ -26,11 +26,11 @@ describe('GddStatus', () => { describe('balance is loading', () => { it('it displays em-dash as the ammount of GDD', () => { - expect(wrapper.findAll('div.card-body').at(0).text()).toEqual('— GDD') + expect(wrapper.find('div.gdd-status-gdd').text()).toEqual('— GDD') }) it('it displays em-dash as the ammount of GDT', () => { - expect(wrapper.findAll('div.card-body').at(1).text()).toEqual('— GDT') + expect(wrapper.find('div.gdd-status-gdt').text()).toEqual('— GDT') }) }) @@ -42,11 +42,11 @@ describe('GddStatus', () => { }) it('it displays the ammount of GDD', () => { - expect(wrapper.findAll('div.card-body').at(0).text()).toEqual('1234 GDD') + expect(wrapper.find('div.gdd-status-gdd').text()).toEqual('1234 GDD') }) it('it displays the ammount of GDT', () => { - expect(wrapper.findAll('div.card-body').at(1).text()).toEqual('9876 GDT') + expect(wrapper.find('div.gdd-status-gdt').text()).toEqual('9876 GDT') }) }) }) diff --git a/frontend/src/views/Pages/GddGdtStatus.vue b/frontend/src/views/Pages/GddGdtStatus.vue new file mode 100644 index 000000000..11a3019c5 --- /dev/null +++ b/frontend/src/views/Pages/GddGdtStatus.vue @@ -0,0 +1,32 @@ + + + diff --git a/frontend/src/views/Pages/SendOverview.spec.js b/frontend/src/views/Pages/SendOverview.spec.js new file mode 100644 index 000000000..0bd43ba2d --- /dev/null +++ b/frontend/src/views/Pages/SendOverview.spec.js @@ -0,0 +1,135 @@ +import { mount } from '@vue/test-utils' +import SendOverview from './SendOverview' + +const sendMock = jest.fn() +sendMock.mockResolvedValue('success') + +const localVue = global.localVue + +// window.scrollTo = jest.fn() + +describe('SendOverview', () => { + let wrapper + + const propsData = { + balance: 123.45, + transactionCount: 1, + } + + const mocks = { + $t: jest.fn((t) => t), + $n: jest.fn((n) => String(n)), + $store: { + state: { + email: 'sender@example.org', + }, + }, + $apollo: { + mutate: sendMock, + }, + } + + const Wrapper = () => { + return mount(SendOverview, { localVue, mocks, propsData }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + it('has a status GDD line', () => { + expect(wrapper.find('div.gdd-status-gdd').exists()).toBeTruthy() + }) + + it('has a send field', () => { + expect(wrapper.find('div.gdd-send').exists()).toBeTruthy() + }) + + // it('has a transactions table', () => { + // expect(wrapper.find('div.gdd-transaction-list').exists()).toBeTruthy() + // }) + + describe('transaction form', () => { + it('steps forward in the dialog', async () => { + await wrapper.findComponent({ name: 'TransactionForm' }).vm.$emit('set-transaction', { + email: 'user@example.org', + amount: 23.45, + memo: 'Make the best of it!', + }) + expect(wrapper.findComponent({ name: 'TransactionConfirmation' }).exists()).toBeTruthy() + }) + }) + + describe('confirm transaction', () => { + beforeEach(() => { + wrapper.setData({ + currentTransactionStep: 1, + transactionData: { + email: 'user@example.org', + amount: 23.45, + memo: 'Make the best of it!', + }, + }) + }) + + it('resets the transaction process when on-reset is emitted', async () => { + await wrapper.findComponent({ name: 'TransactionConfirmation' }).vm.$emit('on-reset') + expect(wrapper.findComponent({ name: 'TransactionForm' }).exists()).toBeTruthy() + expect(wrapper.vm.transactionData).toEqual({ + email: '', + amount: 0, + memo: '', + }) + }) + + describe('transaction is confirmed and server response is success', () => { + beforeEach(async () => { + jest.clearAllMocks() + await wrapper + .findComponent({ name: 'TransactionConfirmation' }) + .vm.$emit('send-transaction') + }) + + it('calls the API when send-transaction is emitted', async () => { + expect(sendMock).toBeCalledWith( + expect.objectContaining({ + variables: { + email: 'user@example.org', + amount: 23.45, + memo: 'Make the best of it!', + }, + }), + ) + }) + + it('emits update-balance', () => { + expect(wrapper.emitted('update-balance')).toBeTruthy() + expect(wrapper.emitted('update-balance')).toEqual([[23.45]]) + }) + + it('shows the succes page', () => { + expect(wrapper.find('div.card-body').text()).toContain('form.send_transaction_success') + }) + }) + + describe('transaction is confirmed and server response is error', () => { + beforeEach(async () => { + jest.clearAllMocks() + sendMock.mockRejectedValue({ message: 'receiver not found' }) + await wrapper + .findComponent({ name: 'TransactionConfirmation' }) + .vm.$emit('send-transaction') + }) + + it('shows the error page', () => { + expect(wrapper.find('div.card-body').text()).toContain('form.send_transaction_error') + }) + + it('shows recipient not found', () => { + expect(wrapper.text()).toContain('transaction.receiverNotFound') + }) + }) + }) + }) +}) diff --git a/frontend/src/views/Pages/SendOverview.vue b/frontend/src/views/Pages/SendOverview.vue index d90dc6adc..aaa16b550 100644 --- a/frontend/src/views/Pages/SendOverview.vue +++ b/frontend/src/views/Pages/SendOverview.vue @@ -1,12 +1,18 @@