Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<template>

<div class="token-input-wrapper">
<KCircularLoader
v-if="loading"
size="38"
class="loader"
/>
<template v-else>
<KTextbox
ref="tokenInput"
:value="displayToken"
readonly
class="notranslate token-input"
label="Token"
:floatingLabel="false"
:appearanceOverrides="{ maxWidth: 'none !important', width: '100% !important' }"
/>
<KIconButton
icon="copy"
:text="$tr('copyTokenButton')"
class="copy-button"
@click="copyToClipboard"
/>
</template>
</div>

</template>


<script>

export default {
name: 'StudioCopyToken',
props: {
token: { type: String, required: true },
loading: { type: Boolean, default: false },
hyphenate: { type: Boolean, default: true },
},
computed: {
displayToken() {
return this.hyphenate ? this.token.slice(0, 5) + '-' + this.token.slice(5) : this.token;
},
successMessage() {
return this.$tr('tokenCopied');
},
},
methods: {
copyToClipboard() {
if (!this.token.trim()) {
this.$store.dispatch('showSnackbarSimple', this.$tr('tokenCopyFailed'));
return;
}
if (navigator.clipboard) {
navigator.clipboard
.writeText(this.displayToken)
.then(() => {
this.$store.dispatch('showSnackbarSimple', this.successMessage);
})
.catch(() => {
this.$store.dispatch('showSnackbarSimple', this.$tr('tokenCopyFailed'));
});
} else {
this.$store.dispatch('showSnackbarSimple', this.$tr('tokenCopyFailed'));
}
},
},
$trs: {
copyTokenButton: 'Copy token',
tokenCopied: 'Token copied!',
tokenCopyFailed: 'Failed to copy token.',
},
};

</script>


<style scoped>

.token-input-wrapper {
display: flex;
align-items: start;
justify-content: center;
}

.loader {
margin: auto;
}

.token-input {
flex: 1;
width: 100%;
max-width: none;
}

.copy-button {
opacity: 0.8;
}

.copy-button:hover {
opacity: 1;
}

</style>
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
rel="noopener noreferrer"
/>
</p>
<CopyToken
<StudioCopyToken
class="copy-token"
:token="user.api_token || ' '"
:loading="!user.api_token"
Expand Down Expand Up @@ -158,15 +158,15 @@
import FullNameForm from './FullNameForm';
import ChangePasswordForm from './ChangePasswordForm';
import DeleteAccountForm from './DeleteAccountForm';
import CopyToken from 'shared/views/CopyToken';
import StudioCopyToken from './StudioCopyToken.vue';

export default {
name: 'Account',
components: {
ChangePasswordForm,
CopyToken,
FullNameForm,
DeleteAccountForm,
StudioCopyToken,
},
data() {
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { mount } from '@vue/test-utils';
import StudioCopyToken from '../Account/StudioCopyToken.vue';

function makeWrapper(props = {}) {
return mount(StudioCopyToken, {
propsData: {
token: 'testtoken',
...props,
},
mocks: {
$store: { dispatch: jest.fn() },
$tr: key => key,
},
});
}

describe('StudioCopyToken', () => {
it('displays hyphenated token by default', () => {
const wrapper = makeWrapper();
const input = wrapper.findComponent({ ref: 'tokenInput' });
expect(input.props('value')).toBe('testt-oken');
});

it('displays token without hyphen if hyphenate is false', () => {
const wrapper = makeWrapper({ hyphenate: false });
const input = wrapper.findComponent({ ref: 'tokenInput' });
expect(input.props('value')).toBe('testtoken');
});

it('shows loader when loading is true', () => {
const wrapper = makeWrapper({ loading: true });
expect(wrapper.findComponent({ name: 'KCircularLoader' }).exists()).toBe(true);
expect(wrapper.findComponent({ ref: 'tokenInput' }).exists()).toBe(false);
});

it('should fire a copy operation on button click', async () => {
const writeText = jest.fn().mockResolvedValue();
Object.assign(navigator, {
clipboard: { writeText },
});
const wrapper = makeWrapper();
await wrapper.find('.copy-button').trigger('click');
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('testt-oken');
});

it('dispatches snackbar on successful copy', async () => {
const writeText = jest.fn().mockResolvedValue();
Object.assign(navigator, {
clipboard: { writeText },
});
const wrapper = makeWrapper();
await wrapper.find('.copy-button').trigger('click');
expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('showSnackbarSimple', 'tokenCopied');
});

it('dispatches snackbar on failed copy', async () => {
const writeText = jest.fn().mockRejectedValue();
Object.assign(navigator, {
clipboard: { writeText },
});
const wrapper = makeWrapper();
await wrapper.find('.copy-button').trigger('click');
expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith(
'showSnackbarSimple',
'tokenCopyFailed',
);
});

it('dispatches snackbar if token is empty', async () => {
const wrapper = makeWrapper({ token: ' ' });
await wrapper.find('.copy-button').trigger('click');
expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith(
'showSnackbarSimple',
'tokenCopyFailed',
);
});
});