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
3 changes: 1 addition & 2 deletions backend/src/permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const permissions = shield(
{
Query: {
"*": forbidden,
posts: isAuthenticated,
posts: allow,
users: isAuthenticated,
},
Mutation: {
Expand All @@ -39,6 +39,5 @@ export const permissions = shield(
},
{
allowExternalErrors: true,
fallbackRule: isAuthenticated,
}
);
10 changes: 0 additions & 10 deletions backend/src/posts.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,6 @@ describe("queries", () => {

let postQuery = () => query({ query: POSTS });

it("throws error when user is not authorised", async () => {
userId = null;
await expect(postQuery()).resolves.toMatchObject({
data: {
posts: null,
},
errors: [expect.objectContaining({ message: "Not Authorised!" })],
});
});

it("returns empty array", async () => {
await expect(postQuery()).resolves.toMatchObject({
errors: undefined,
Expand Down
3 changes: 2 additions & 1 deletion webapp/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ npm-debug.log*
storybook-static

### Nuxt.js###
.nuxt
.nuxt
static/sw.js
42 changes: 31 additions & 11 deletions webapp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,39 @@

## Build Setup

```bash
# install dependencies
$ npm install
### install dependencies

# serve with hot reload at localhost:3000
$ npm run dev
```
npm install
```

### serve with hot reload at localhost:3000

```
npm run dev
```

### build for production and launch server

```
npm run build
npm run start
```
Comment on lines +17 to +22
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

⭐ For instructions in the README.md on how to build your webapp for production.


# build for production and launch server
$ npm run build
$ npm run start
### generate static project

# generate static project
$ npm run generate
```
npm run generate
```

### Lint files

For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org).
```
npm run lint
```

### Run Storybook

```
npm run storybook
```
75 changes: 75 additions & 0 deletions webapp/components/LoginForm/LoginForm.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { createLocalVue, shallowMount } from "@vue/test-utils";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job!

⭐ ⭐ For a login feature in your webapp including a Vue component and its software tests.

import Vuex from "vuex";
import LoginForm from "./LoginForm.vue";

const localVue = createLocalVue();
localVue.use(Vuex);

describe("LoginForm.vue", () => {
let wrapper;
let actions;
let store;

const createComponent = () => {
store = new Vuex.Store({
actions,
});
wrapper = shallowMount(LoginForm, {
store,
localVue,
});
};

afterEach(() => {
wrapper.destroy();
});

it("renders a Vue component", () => {
createComponent();
expect(wrapper.exists()).toBe(true);
});

it("renders an email input field", () => {
createComponent();
const emailInput = wrapper.find("input#email");
expect(emailInput.exists()).toBe(true);
});

it("renders a password input field", () => {
createComponent();
const passwordInput = wrapper.find("input#password");
expect(passwordInput.exists()).toBe(true);
});

it("renders a submit button", () => {
createComponent();
const submitBtn = wrapper.find("input#submit");
expect(submitBtn.exists()).toBe(true);
});

describe("invalid credentials", () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

valid credentials would be nice.

let login;
beforeEach(() => {
login = jest.fn().mockRejectedValue(new Error("test"));
actions = {
login,
};
createComponent();
});

describe("click submit button", () => {
beforeEach(() => {
const submitBtn = wrapper.find("input#submit");
submitBtn.trigger("click");
});
it("calls login", () => {
expect(login.mock.calls.length).toBe(1);
});
it("renders error message", () => {
const invCredentialsMsg = wrapper.find("#invCredentialsMsg");
expect(invCredentialsMsg.exists()).toBe(true);
expect(invCredentialsMsg.text()).toContain("Falsche Email oder Passwort");
});
});
});
});
64 changes: 64 additions & 0 deletions webapp/components/LoginForm/LoginForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<template>
<div>
<form onsubmit="event.preventDefault();">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<form onsubmit="event.preventDefault();">
<form @submit.prevent="submit">

<input
id="email"
type="email"
aria-label="Email"
v-model="email"
placeholder="Email"
/>
<input
id="password"
type="password"
aria-label="Password"
v-model="password"
placeholder="Password"
/>
<div id="invCredentialsMsg" v-if="invalidCredentials">
Falsche Email oder Passwort
</div>
<input
id="submit"
type="submit"
aria-label="Login"
value="Login"
@click="submit"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have the submit on the <form> you can omit that. With the form submit, you could also submit by hitting enter.

/>
</form>
</div>
</template>

<script>
import { mapActions } from "vuex";
export default {
methods: {
...mapActions(["login"]),
submit: async function () {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
submit: async function () {
async submit() {

try {
await this.login({
email: this.email,
password: this.password,
apolloClient: this.$apollo,
});
this.$router.push({
path: "/",
});
this.invalidCredentials = false;
} catch (error) {
this.invalidCredentials = true;
}
},
},
data: function () {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
data: function () {
data() {

return {
email: "",
password: "",
invalidCredentials: false,
};
},
};
</script>

<style>
</style>
86 changes: 86 additions & 0 deletions webapp/components/NavBar/NavBar.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { createLocalVue, shallowMount, RouterLinkStub } from "@vue/test-utils";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great,

⭐ ⭐ For a menu component which shows a login or logout button and its software tests.

import Vuex from "vuex";
import NavBar from "./NavBar.vue";

const localVue = createLocalVue();
localVue.use(Vuex);

describe("NavBar.vue", () => {
let wrapper;
let actions;
let getters;
let store;

const createComponent = () => {
store = new Vuex.Store({
actions,
getters,
});
wrapper = shallowMount(NavBar, {
store,
localVue,
stubs: {
NuxtLink: RouterLinkStub,
},
});
};
beforeEach(() => {
getters = {
loggedIn: () => false,
};
});

afterEach(() => {
wrapper.destroy();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess, if you would return the wrapper in your setup method, you don't run into issues of re-using an existing wrapper. So you wouldn't need this safety measure here.

});

it("renders a Vue component", () => {
createComponent();
expect(wrapper.exists()).toBe(true);
Comment on lines +38 to +39
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to have my setup methods to return the test subject:

Suggested change
createComponent();
expect(wrapper.exists()).toBe(true);
const wrapper = createComponent();
expect(wrapper.exists()).toBe(true);

});

describe("not logged in", () => {
beforeEach(() => createComponent());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
beforeEach(() => createComponent());
beforeEach(createComponent);


it("shows login link", () => {
const loginLink = wrapper.findComponent(RouterLinkStub);
expect(loginLink.exists()).toBe(true);
});
it("links to /login page", () => {
const loginLink = wrapper.findComponent(RouterLinkStub);
expect(loginLink.props().to).toBe("/login");
});
it("does not show logout button", () => {
const logoutBtn = wrapper.find("#logoutBtn");
expect(logoutBtn.exists()).toBe(false);
});
});

describe("logged in", () => {
let logout = jest.fn();

beforeEach(() => {
getters = {
loggedIn: () => true,
};
actions = {
logout,
};
createComponent();
});

it("shows logout button", () => {
const logoutBtn = wrapper.find("#logoutBtn");
expect(logoutBtn.exists()).toBe(true);
});
it("calls logout when logout button is clicked", () => {
const logoutBtn = wrapper.find("#logoutBtn");
logoutBtn.trigger("click");
expect(logout.mock.calls.length).toBe(1);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
expect(logout.mock.calls.length).toBe(1);
expect(logout).toBeCalledTimes(1);

is the idiomatic way I think

});
it("does not show login link", () => {
const loginLink = wrapper.findComponent(RouterLinkStub);
expect(loginLink.exists()).toBe(false);
});
});
});
26 changes: 26 additions & 0 deletions webapp/components/NavBar/NavBar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<template>
<div>
<template v-if="loggedIn">
<button id="logoutBtn" @click="logout">Logout</button>
</template>
<template v-else>
<NuxtLink to="/login">Login</NuxtLink>
</template>
</div>
Comment on lines +2 to +9
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<div>
<template v-if="loggedIn">
<button id="logoutBtn" @click="logout">Logout</button>
</template>
<template v-else>
<NuxtLink to="/login">Login</NuxtLink>
</template>
</div>
<div>
<button v-if="loggedIn" id="logoutBtn" @click="logout">Logout</button>
<NuxtLink v-else to="/login">Login</NuxtLink>
</div>

You could even remove the <div> if you want. An if-clause with an else is allowed as root node.

</template>

<script>
import { mapGetters, mapActions } from "vuex";

export default {
computed: {
...mapGetters(["loggedIn"]),
},
methods: {
...mapActions(["logout"]),
},
};
</script>

<style>
</style>
Comment on lines +25 to +26
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<style>
</style>

Loading