Skip to content

Commit

Permalink
✅ Bootstrap Tests
Browse files Browse the repository at this point in the history
  • Loading branch information
0x46616c6b committed Feb 27, 2022
1 parent fd438a1 commit c4b433d
Show file tree
Hide file tree
Showing 11 changed files with 2,688 additions and 2,814 deletions.
27 changes: 26 additions & 1 deletion .github/workflows/integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,37 @@ on:
pull_request:

jobs:
test:
name: Test
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/[email protected]

- name: Set up Node
uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'yarn'

- name: Install Dependencies
run: yarn install --frozen-lockfile

- name: Test
run: yarn run test --coverage

- name: Codecov
uses: codecov/[email protected]
with:
token: ${{ secrets.CODECOV_TOKEN }}

build:
name: Build
runs-on: ubuntu-20.04
needs: [test]
strategy:
matrix:
node-version: ['12', '14', '16']
node-version: [ '12', '14', '16' ]

steps:
- name: Checkout
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*

react-app-env.d.ts
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
"typescript": "^4.1.2"
},
"devDependencies": {
"@testing-library/dom": "^8.11.3",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.3",
"@types/jest": "^27.4.1",
"@typescript-eslint/eslint-plugin": "^4.28.1",
"@typescript-eslint/parser": "^4.22.0",
"eslint": "^7.29.0",
Expand All @@ -39,6 +43,12 @@
"eject": "react-scripts eject",
"lint": "eslint --ext=ts,tsx src"
},
"coverageReporters": [
"clover",
"json",
"lcov",
"text"
],
"browserslist": {
"production": [
">0.2%",
Expand Down
95 changes: 95 additions & 0 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from 'react'
import App from './App'
import { render, screen } from '@testing-library/react'
import * as api from './lib/api'
import { Settings, Ticker } from './lib/types'

describe('App', function () {
const initSettings = {
refresh_interval: 1000,
inactive_settings: {
author: 'Systemli Ticker Team',
email: '[email protected]',
homepage: '',
twitter: '',
headline: 'The ticker is currently inactive.',
sub_headline: 'Please contact us if you want to use it.',
description: '...',
},
} as Settings
const ticker = {
id: '1',
active: true,
creation_date: new Date(),
title: 'Ticker Title',
description: 'Ticker Description',
domain: 'example.com',
information: {
author: 'Systemli Ticker Team',
url: '',
email: '',
twitter: '',
facebook: '',
},
} as Ticker

test('renders OfflineView', async function () {
jest.spyOn(api, 'getInit').mockRejectedValue(new TypeError())
render(<App />)

expect(screen.getByText('Loading')).toBeInTheDocument()

expect(
await screen.findByText('It seems that you are offline.')
).toBeInTheDocument()
})

test('renders InactiveView', async function () {
jest.spyOn(api, 'getInit').mockResolvedValue({
data: {
settings: initSettings,
ticker: null,
},
})
render(<App />)

expect(screen.getByText('Loading')).toBeInTheDocument()

expect(
await screen.findByText('The ticker is currently inactive.')
).toBeInTheDocument()
})

test('renders ActiveView', async function () {
jest.spyOn(api, 'getInit').mockResolvedValue({
data: {
settings: initSettings,
ticker: ticker,
},
})
jest.spyOn(api, 'getTimeline').mockResolvedValue({
data: {
messages: [],
},
})
const intersectionObserverMock = () => ({
observe: () => null,
})
window.IntersectionObserver = jest
.fn()
.mockImplementation(intersectionObserverMock)
render(<App />)

expect(screen.getByText('Loading')).toBeInTheDocument()

expect(
await screen.findByText(
'The messages update automatically. There is no need to reload the entire page.'
)
).toBeInTheDocument()

expect(
screen.getByText('We dont have any messages at the moment.')
).toBeInTheDocument()
})
})
26 changes: 11 additions & 15 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { FC, useEffect, useState } from 'react'
import { Container, Dimmer, Loader } from 'semantic-ui-react'
// import * as OfflinePluginRuntime from 'offline-plugin/runtime'
import { apiUrl } from './lib/helper'
import { Ticker, Settings } from './lib/types'
import { Settings, Ticker } from './lib/types'
import { ActiveView, ErrorView, InactiveView } from './views'
import { getInit } from './lib/api'

const App: FC = () => {
const [ticker, setTicker] = useState<Ticker | null>(null)
Expand All @@ -22,21 +22,13 @@ const App: FC = () => {
// })

const fetchInit = () => {
const url = `${apiUrl}/init`
fetch(url)
getInit()
.then(response => {
if (!response.ok) {
setGotError(true)
return
}
return response.json()
})
.then(response => {
if (response.data?.settings) {
if (response.data.settings) {
setSettings(response.data.settings)
}

if (response.data?.ticker?.active) {
if (response.data.ticker?.active) {
setTicker(response.data.ticker)
if (ticker?.title) {
document.title = ticker.title
Expand All @@ -45,8 +37,12 @@ const App: FC = () => {

setIsLoading(false)
})
.catch(() => {
setIsOffline(true)
.catch(error => {
if (error instanceof TypeError) {
setIsOffline(true)
} else {
setGotError(true)
}
setIsLoading(false)
})
}
Expand Down
25 changes: 25 additions & 0 deletions src/components/MessageList.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react'
import * as api from '../lib/api'
import MessageList from './MessageList'
import { render, screen } from '@testing-library/react'

describe('MessageList', function () {
test('renders empty Messages', async function () {
jest.spyOn(api, 'getTimeline').mockResolvedValue({
data: { messages: [] },
})
const intersectionObserverMock = () => ({
observe: () => null,
})
window.IntersectionObserver = jest
.fn()
.mockImplementation(intersectionObserverMock)

render(<MessageList refreshInterval={10} />)

expect(screen.getByText('Loading messages')).toBeInTheDocument()
expect(
await screen.findByText('We dont have any messages at the moment.')
).toBeInTheDocument()
})
})
22 changes: 9 additions & 13 deletions src/components/MessageList.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { FC, useState, useEffect, useCallback, useRef } from 'react'
import { FC, useCallback, useEffect, useRef, useState } from 'react'
import { Dimmer, Header, Icon, Loader, Segment } from 'semantic-ui-react'
import { apiUrl } from '../lib/helper'
import { Message as MessageType } from '../lib/types'
import Message from './Message'
import { getTimeline } from '../lib/api'

interface Props {
refreshInterval: number
Expand All @@ -11,21 +11,18 @@ interface Props {
const MessageList: FC<Props> = props => {
const [isLoading, setIsLoading] = useState<boolean>(true)
const [messages, setMessages] = useState<MessageType[]>([])
const [lastMessageReceived, setLastMessageReceived] = useState<boolean>(
false
)
const [lastMessageReceived, setLastMessageReceived] =
useState<boolean>(false)

const loadMoreSpinnerRef = useRef<HTMLDivElement>(null)

const fetchMessages = useCallback(() => {
const after = messages[0]?.id
const url = `${apiUrl}/timeline` + (after ? `?after=${after}` : '')

fetch(url)
.then(response => response.json())
getTimeline({ after: after ? after : null })
.then(response => {
if (response.data?.messages) {
setMessages([...response.data?.messages, ...messages])
if (response.data.messages) {
setMessages([...response.data.messages, ...messages])
}
setIsLoading(false)
})
Expand All @@ -39,10 +36,9 @@ const MessageList: FC<Props> = props => {
const fetchOlderMessages = useCallback(() => {
const oldestMessage = messages[messages.length - 1]
if (oldestMessage !== undefined) {
fetch(`${apiUrl}/timeline?before=${oldestMessage.id}`)
.then(response => response.json())
getTimeline({ before: oldestMessage.id })
.then(response => {
if (response.data?.messages !== null) {
if (response.data.messages !== null) {
setMessages([...messages, ...response.data.messages])
} else {
setLastMessageReceived(true)
Expand Down
57 changes: 57 additions & 0 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Message, Settings, Ticker } from './types'

const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:8080/v1'

type InitResponseData = {
settings: Settings
ticker: Ticker | null
}

export type InitResponse = {
data: InitResponseData
}

type TimelineResponseData = {
messages: Message[]
}

export type TimelineResponse = {
data: TimelineResponseData
}

async function get<T>(path: string, config?: RequestInit): Promise<T> {
const init = { method: 'get', ...config }
const request = new Request(path, init)
const response = await fetch(request)

if (!response.ok) {
throw new Error(
`The server responses with an error: ${response.statusText} (${response.status})`
)
}

return response.json().catch(() => ({}))
}

export async function getInit(): Promise<InitResponse> {
return get(`${apiUrl}/init`)
}

export type TimelineOpts = {
after?: string | null
before?: string | null
}

export async function getTimeline(
opts: TimelineOpts
): Promise<TimelineResponse> {
if (opts.after != null) {
return get(`${apiUrl}/timeline?after=${opts.after}`)
}

if (opts.before != null) {
return get(`${apiUrl}/timeline?before=${opts.before}`)
}

return get(`${apiUrl}/timeline`)
}
3 changes: 0 additions & 3 deletions src/lib/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ export const isMobile = (): boolean => {
return width < breakpoints.mobile
}

export const apiUrl =
process.env.REACT_APP_API_URL || 'http://localhost:8080/v1'

// FIXME: Might be better to use a library like validator.js
// to catch more cases.
export const replaceMagic = (text: string): string => {
Expand Down
5 changes: 5 additions & 0 deletions src/setupTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom'
Loading

0 comments on commit c4b433d

Please sign in to comment.