Skip to content
This repository was archived by the owner on Dec 14, 2023. It is now read-only.

Commit 55ca7fd

Browse files
author
Glen Pike
authored
Merge pull request #312 from CoderDojo/text-direction-component
Text direction component
2 parents 4c0dff4 + 5b9a419 commit 55ca7fd

File tree

12 files changed

+222
-32
lines changed

12 files changed

+222
-32
lines changed

src/App.vue

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
<template>
22
<div id="app">
3-
<cd-header></cd-header>
4-
<div class="cd-menu__content-container">
5-
<router-view></router-view>
6-
<cd-cookie-notice></cd-cookie-notice>
7-
</div>
8-
<cd-footer></cd-footer>
3+
<cd-text-direction-wrapper>
4+
<cd-header></cd-header>
5+
<div class="cd-menu__content-container">
6+
<router-view></router-view>
7+
<cd-cookie-notice></cd-cookie-notice>
8+
</div>
9+
<cd-footer></cd-footer>
10+
</cd-text-direction-wrapper>
911
</div>
1012
</template>
1113

@@ -14,11 +16,13 @@
1416
import cdHeader from './common/cd-header';
1517
import cdFooter from './common/cd-footer';
1618
import cdCookieNotice from './common/cd-cookie-notice';
19+
import cdTextDirectionWrapper from './common/cd-text-direction-wrapper';
1720
1821
export default {
1922
name: 'app',
2023
store,
2124
components: {
25+
cdTextDirectionWrapper,
2226
cdHeader,
2327
cdFooter,
2428
cdCookieNotice,

src/common/cd-covid-banner.vue

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,34 @@
11
<template>
22
<div class="cd-banner">
3-
<p>
4-
{{ $t('Thank you for your interest in joining the CoderDojo community!') }}
5-
{{ $t('Due to the coronavirus pandemic, there are different restrictions in place across the world.') }}
6-
{{ $t('Please follow the appropriate public health advice for your country or region before planning in-person club events.') }}
7-
</p>
8-
<p>
9-
{{ $t('If you are ready to hold in-person club events soon, please') }}
10-
<a class="cd-banner-link" href="https://zen.coderdojo.com/dashboard/start-dojo">{{ $t('register your details') }}</a>
11-
{{ $t('and we will be in touch.') }}
12-
</p>
13-
<p>
14-
{{ $t('If you’re not able to start your Dojo in person yet, there are many other ways that you can get involved in the meantime.') }}
15-
{{ $t('Check out the exciting opportunities for young people, parents, volunteers, and educators to learn and get creative with tech through') }}
16-
<a class="cd-banner-link" target="_blank" href="https://www.raspberrypi.org/learn/?utm_source=coderdojo&amp;utm_medium=covid-banner&amp;utm_campaign=dmah">{{ $t('Digital Making at Home') }}</a>
17-
{{ $t('from the Raspberry Pi Foundation.') }}
18-
</p>
3+
<cd-text-direction-wrapper direction="ltr">
4+
<p>
5+
{{ $t('Thank you for your interest in joining the CoderDojo community!') }}
6+
{{ $t('Due to the coronavirus pandemic, there are different restrictions in place across the world.') }}
7+
{{ $t('Please follow the appropriate public health advice for your country or region before planning in-person club events.') }}
8+
</p>
9+
<p>
10+
{{ $t('If you are ready to hold in-person club events soon, please') }}
11+
<a class="cd-banner-link" href="https://zen.coderdojo.com/dashboard/start-dojo">{{ $t('register your details') }}</a>
12+
{{ $t('and we will be in touch.') }}
13+
</p>
14+
<p>
15+
{{ $t('If you’re not able to start your Dojo in person yet, there are many other ways that you can get involved in the meantime.') }}
16+
{{ $t('Check out the exciting opportunities for young people, parents, volunteers, and educators to learn and get creative with tech through') }}
17+
<a class="cd-banner-link" target="_blank" href="https://www.raspberrypi.org/learn/?utm_source=coderdojo&amp;utm_medium=covid-banner&amp;utm_campaign=dmah">{{ $t('Digital Making at Home') }}</a>
18+
{{ $t('from the Raspberry Pi Foundation.') }}
19+
</p>
20+
</cd-text-direction-wrapper>
1921
</div>
2022
</template>
2123

2224
<script>
25+
import cdTextDirectionWrapper from './cd-text-direction-wrapper';
26+
27+
export default {
28+
components: {
29+
cdTextDirectionWrapper,
30+
},
31+
};
2332
</script>
2433

2534
<style scoped lang="less">
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<template>
2+
<div class="lang-wrapper" v-bind:dir="textDirection">
3+
<slot></slot>
4+
</div>
5+
</template>
6+
7+
<script>
8+
export default {
9+
name: 'TextDirectionWrapper',
10+
props: {
11+
direction: String,
12+
},
13+
computed: {
14+
textDirection() {
15+
if (this.direction) {
16+
return this.direction;
17+
}
18+
19+
const config = this.$store.getters.chosenLanguageConfig;
20+
let direction = 'ltr';
21+
if (config && config.dir) {
22+
direction = config.dir;
23+
}
24+
return direction;
25+
},
26+
},
27+
};
28+
</script>
29+
30+
<style>
31+
</style>

src/locale/cd-lang-picker.vue

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<div class="col-md-4 col-sm-6">
44
<span class="fa fa-language fa-2x cd-lang-picker__icon"></span>
55
<select class="cd-lang-picker__select" v-model="lang">
6-
<option v-for="availableLanguage in availableLanguages" :value="availableLanguage.code">{{ availableLanguage.name }} ({{ availableLanguage.country }})</option>
6+
<option v-for="availableLanguage in availableLanguages" :value="availableLanguage.code" v-bind:key="availableLanguage.code">{{ availableLanguage.name }} ({{ availableLanguage.country }})</option>
77
</select>
88
</div>
99
<div class="col-md-8 col-sm-6">
@@ -52,22 +52,26 @@
5252
this.setMomentLocale(val);
5353
this.$i18n.setLocaleMessage(val, strings);
5454
this.$i18n.locale = val;
55+
const matchingLanguageConfig = find(this.availableLanguages,
56+
language => language.code === val);
57+
this.$store.dispatch('updateChosenLanguageConfig', matchingLanguageConfig);
5558
});
5659
},
5760
},
5861
async created() {
5962
this.availableLanguages = (await this.getAvailableLanguages()).body;
6063
const browserLocale = navigator.language.replace('-', '_');
61-
const matchingLocale = find(this.availableLanguages,
64+
const matchingLanguageConfig = find(this.availableLanguages,
6265
language => language.code === browserLocale);
6366
const langCookie = Cookie.get('NG_TRANSLATE_LANG_KEY');
6467
if (langCookie) {
6568
this.lang = langCookie.substring(1, langCookie.length - 1);
66-
} else if (matchingLocale) {
69+
} else if (matchingLanguageConfig) {
6770
this.lang = browserLocale;
6871
} else {
6972
this.lang = 'en_US';
7073
}
74+
this.$store.dispatch('updateChosenLanguageConfig', matchingLanguageConfig);
7175
},
7276
};
7377
</script>

src/store/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Vue from 'vue';
22
import Vuex from 'vuex';
33
import order from './modules/order';
4+
import language from './modules/language';
45

56
Vue.use(Vuex);
67

@@ -52,5 +53,6 @@ export default new Vuex.Store({
5253
},
5354
modules: {
5455
order,
56+
language,
5557
},
5658
});

src/store/modules/language.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Vue from 'vue';
2+
3+
export const mutations = {
4+
setChosenLanguageConfig(state, chosenLanguageConfig) {
5+
Vue.set(state, 'chosenLanguageConfig', chosenLanguageConfig);
6+
},
7+
};
8+
9+
export const actions = {
10+
updateChosenLanguageConfig({ commit }, chosenLanguageConfig) {
11+
commit('setChosenLanguageConfig', chosenLanguageConfig);
12+
},
13+
};
14+
15+
export const getters = {
16+
chosenLanguageConfig: state => state.chosenLanguageConfig,
17+
};
18+
19+
export default {
20+
namespaced: false,
21+
state: {
22+
chosenLanguageConfig: null,
23+
},
24+
mutations,
25+
actions,
26+
getters,
27+
};

test/unit/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import Vue from 'vue';
22
import Vuex from 'vuex';
33
import VueResource from 'vue-resource';
4+
import chai from 'chai';
5+
import sinonChai from 'sinon-chai';
6+
7+
chai.use(sinonChai);
48

59
/* eslint-disable no-extend-native, no-param-reassign, func-names */
610
if (!String.prototype.endsWith) {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import vueUnitHelper from 'vue-unit-helper';
2+
import TextDirectionWrapper from '@/common/cd-text-direction-wrapper';
3+
4+
describe('Text Direction Wrapper', () => {
5+
describe('computed.textDirection', () => {
6+
context('when chosen language specifies a direction', () => {
7+
it('returns that value', () => {
8+
const vm = vueUnitHelper(TextDirectionWrapper);
9+
vm.$store = {
10+
getters: {
11+
get chosenLanguageConfig() { return { dir: 'test' }; },
12+
},
13+
};
14+
expect(vm.textDirection).to.eql('test');
15+
});
16+
});
17+
18+
context('when chosen language doesn\'t specify a direction', () => {
19+
it('returns default', () => {
20+
const vm = vueUnitHelper(TextDirectionWrapper);
21+
vm.$store = {
22+
getters: {
23+
get chosenLanguageConfig() { return { }; },
24+
},
25+
};
26+
expect(vm.textDirection).to.eql('ltr');
27+
});
28+
});
29+
30+
context('when initialised with a prop', () => {
31+
it('overrides the chosen language direction', () => {
32+
const vm = vueUnitHelper(TextDirectionWrapper);
33+
vm.direction = 'down';
34+
vm.$store = {
35+
getters: {
36+
get chosenLanguageConfig() { return { dir: 'test' }; },
37+
},
38+
};
39+
expect(vm.textDirection).to.eql('down');
40+
});
41+
});
42+
});
43+
});

test/unit/specs/locale/cd-lang-picker.spec.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import vueUnitHelper from 'vue-unit-helper';
22
import LangPicker from '!!vue-loader?inject!@/locale/cd-lang-picker';
3+
import { expect } from 'chai';
4+
import Vuex from 'vuex';
35

46
describe('Lang Picker', () => {
57
let CookieMock;
68
let LocaleServiceMock;
79
let MomentMock;
810
let LangPickerWithMocks;
11+
const StoreMock = new Vuex.Store();
912

1013
beforeEach(() => {
1114
CookieMock = {
@@ -23,6 +26,8 @@ describe('Lang Picker', () => {
2326
'./service': LocaleServiceMock,
2427
moment: MomentMock,
2528
});
29+
30+
StoreMock.dispatch = sinon.stub();
2631
});
2732

2833
afterEach(() => {
@@ -74,6 +79,7 @@ describe('Lang Picker', () => {
7479
};
7580
LocaleServiceMock.getStrings.withArgs('es_ES').returns(Promise.resolve({ body: stringsMock }));
7681
sinon.stub(vm, 'setMomentLocale');
82+
vm.$store = StoreMock;
7783

7884
// ACT
7985
vm.$watchers.lang('es_ES');
@@ -89,6 +95,7 @@ describe('Lang Picker', () => {
8995
foo: 'foo',
9096
});
9197
expect(vm.$i18n.locale).to.equal('es_ES');
98+
expect(StoreMock.dispatch).to.have.been.calledWith('updateChosenLanguageConfig');
9299
done();
93100
});
94101
});
@@ -99,6 +106,7 @@ describe('Lang Picker', () => {
99106
it('should recover languages from the API', async () => {
100107
// ARRANGE
101108
const vm = vueUnitHelper(LangPickerWithMocks);
109+
vm.$store = StoreMock;
102110
CookieMock.get.withArgs('NG_TRANSLATE_LANG_KEY').returns('"es_ES"');
103111
vm.getAvailableLanguages = sinon.stub().resolves({ body: ['a'] });
104112
// ACT
@@ -115,16 +123,19 @@ describe('Lang Picker', () => {
115123
CookieMock.get.withArgs('NG_TRANSLATE_LANG_KEY').returns('"es_ES"');
116124
vm.getAvailableLanguages = sinon.stub().resolves({ body: [{ code: 'es_ES' }] });
117125

126+
vm.$store = StoreMock;
118127
// ACT
119128
await vm.$lifecycleMethods.created();
120129

121130
// ASSERT
122131
expect(vm.lang).to.equal('es_ES');
132+
expect(StoreMock.dispatch).to.have.been.calledWith('updateChosenLanguageConfig');
123133
});
124134

125135
it('should set lang to the browser locale if no cookie, and locale matches an available one', async () => {
126136
// ARRANGE
127137
const vm = vueUnitHelper(LangPickerWithMocks);
138+
vm.$store = StoreMock;
128139
CookieMock.get.withArgs('NG_TRANSLATE_LANG_KEY').returns(undefined);
129140
vm.getAvailableLanguages = sinon.stub().resolves({ body: [{ code: 'it_IT' }] });
130141
Object.defineProperty(window.navigator, 'language', { value: 'it-IT', configurable: true });
@@ -134,11 +145,13 @@ describe('Lang Picker', () => {
134145

135146
// ASSERT
136147
expect(vm.lang).to.equal('it_IT');
148+
expect(StoreMock.dispatch).to.have.been.calledWith('updateChosenLanguageConfig');
137149
});
138150

139151
it('should set lang to en_US if no cookie and browser locale doesnt match available lang', async () => {
140152
// ARRANGE
141153
const vm = vueUnitHelper(LangPickerWithMocks);
154+
vm.$store = StoreMock;
142155
vm.getAvailableLanguages = sinon.stub().resolves({ body: [{ code: 'en_US' }] });
143156
CookieMock.get.withArgs('NG_TRANSLATE_LANG_KEY').returns(undefined);
144157
Object.defineProperty(window.navigator, 'language', { value: 'zh-CN', configurable: true });
@@ -148,6 +161,7 @@ describe('Lang Picker', () => {
148161

149162
// ASSERT
150163
expect(vm.lang).to.equal('en_US');
164+
expect(StoreMock.dispatch).to.have.been.calledWith('updateChosenLanguageConfig');
151165
});
152166
});
153167
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { mutations, actions, getters } from '@/store/modules/language';
2+
import { expect } from 'chai';
3+
4+
describe('Language Store', () => {
5+
describe('mutations', () => {
6+
const { setChosenLanguageConfig } = mutations;
7+
it('setChosenLanguageConfig', () => {
8+
const state = { chosenLanguageConfig: null };
9+
const newLanguageConfig = { dir: 'test' };
10+
11+
setChosenLanguageConfig(state, newLanguageConfig);
12+
13+
expect(state.chosenLanguageConfig.dir).to.eq('test');
14+
});
15+
});
16+
17+
describe('actions', () => {
18+
it('updateChosenLanguageConfig', () => {
19+
const commit = sinon.spy();
20+
const state = {};
21+
22+
const chosenLanguageConfig = { dir: 'down' };
23+
24+
actions.updateChosenLanguageConfig({ commit, state }, chosenLanguageConfig);
25+
26+
expect(commit.args).to.deep.equal([['setChosenLanguageConfig', chosenLanguageConfig]]);
27+
});
28+
});
29+
30+
describe('getters', () => {
31+
it('chosenLanguageConfig', () => {
32+
const state = { chosenLanguageConfig: { dir: 'left' } };
33+
expect(getters.chosenLanguageConfig(state).dir).to.eq('left');
34+
});
35+
});
36+
});

0 commit comments

Comments
 (0)