Skip to content

Commit

Permalink
feat: footer & keyboardkeys (#86)
Browse files Browse the repository at this point in the history
* Add Footer and KeyboardKeys components

* Fix lint issues

* Fix typo
  • Loading branch information
tomaszantas authored Apr 25, 2022
1 parent 21e3070 commit a39f092
Show file tree
Hide file tree
Showing 21 changed files with 420 additions and 35 deletions.
70 changes: 70 additions & 0 deletions applications/launchpad_v2/__tests__/mocks/mockTauriIPC.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { mockIPC } from '@tauri-apps/api/mocks'

/**
* Set of default values returned by `tauriIPCMock()`
*/
export const defaultTauriMockValues: Record<string, unknown> = {
os: {
arch: 'x86_64',
platform: 'darwin',
ostype: 'Darwin',
},
}

/**
* The Tauri IPC mock.
*
* It uses Tauri's mockIPC and returns the value set in the `props`.
* If nothing found in `props`, it will return a value from `defaultTauriMockValues`.
*
* @param {Record<string, unknown>} props - pass the value you expect in tests
*
* @example
* // Use default values:
* tauriIPCMock()
*
* // Get given value from specific API module (ie. 'platform' from 'os' module)
* tauriIPCMock({
* os: {
* platform: 'darwin',
* },
* })
*/
export const tauriIPCMock = (props: Record<string, unknown> = undefined) => {
return mockIPC((cmd, args) => {
switch (cmd) {
case 'tauri':
return tauriCmdMock(cmd, args, props)
case 'invoke':
return
default:
return
}
})
}

const tauriCmdMock = (
cmd: string,
args: Record<string, unknown>,
props: Record<string, unknown>,
) => {
const tauriModule = (args?.__tauriModule as string)?.toLowerCase()
const messageCmd = (args?.message as { cmd?: string })?.cmd?.toLowerCase()

if (tauriModule && messageCmd) {
if (
props &&
Object.keys(props).includes(tauriModule) &&
Object.keys(props[tauriModule]).includes(messageCmd)
) {
return props[tauriModule][messageCmd]
} else if (
Object.keys(defaultTauriMockValues).includes(tauriModule) &&
Object.keys(defaultTauriMockValues[tauriModule]).includes(messageCmd)
) {
return defaultTauriMockValues[tauriModule][messageCmd]
}
}

return
}
24 changes: 14 additions & 10 deletions applications/launchpad_v2/src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react'
import { render } from '@testing-library/react'
import { act, render } from '@testing-library/react'
import { randomFillSync } from 'crypto'
import { clearMocks } from '@tauri-apps/api/mocks'
import { ThemeProvider } from 'styled-components'
import { Provider } from 'react-redux'

import App from './App'
import { Provider } from 'react-redux'

import { tauriIPCMock } from '../__tests__/mocks/mockTauriIPC'

import { store } from './store'
import themes from './styles/themes'
Expand All @@ -23,11 +24,14 @@ afterEach(() => {
})

test('renders without crashing', async () => {
render(
<Provider store={store}>
<ThemeProvider theme={themes.light}>
<App />
</ThemeProvider>
</Provider>,
)
tauriIPCMock()
await act(async () => {
render(
<Provider store={store}>
<ThemeProvider theme={themes.light}>
<App />
</ThemeProvider>
</Provider>,
)
})
})
59 changes: 56 additions & 3 deletions applications/launchpad_v2/src/components/Footer/Footer.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,65 @@
import { render, screen } from '@testing-library/react'
import { clearMocks } from '@tauri-apps/api/mocks'
import { act, render, screen } from '@testing-library/react'
import { randomFillSync } from 'crypto'

import Footer from '.'
import { tauriIPCMock } from '../../../__tests__/mocks/mockTauriIPC'

beforeAll(() => {
window.crypto = {
getRandomValues: function (buffer) {
return randomFillSync(buffer)
},
}
})

afterEach(() => {
clearMocks()
})

describe('Footer', () => {
it('should render without crash', () => {
render(<Footer />)
it('should render without crashing', async () => {
tauriIPCMock()

await act(async () => {
render(<Footer />)
})

const el = screen.getByTestId('footer-cmp')
expect(el).toBeInTheDocument()
})

it('should render text for supported OS type', async () => {
tauriIPCMock({
os: {
arch: 'x86_64',
platform: 'darwin',
ostype: 'Darwin',
},
})

await act(async () => {
render(<Footer />)
})

const el = screen.getByTestId('terminal-instructions-in-footer')
expect(el).toBeInTheDocument()
})

it('should NOT render any instructions if met unsupported OS type', async () => {
tauriIPCMock({
os: {
arch: 'x86_64',
platform: 'darwin',
ostype: 'unsupported',
},
})

await act(async () => {
render(<Footer />)
})

const el = screen.queryByTestId('terminal-instructions-in-footer')
expect(el).not.toBeInTheDocument()
})
})
84 changes: 82 additions & 2 deletions applications/launchpad_v2/src/components/Footer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,89 @@
import { StyledFooter } from './styles'
import { useEffect, useState } from 'react'
import { useSpring } from 'react-spring'
import { os } from '@tauri-apps/api'

import KeyboardKeys from '../KeyboardKeys'
import Text from '../Text'

import t from '../../locales'

import { FooterTextWrapper, StyledFooter } from './styles'

const TerminalInstructions = {
linux: {
text: t.footer.toOpenTerminal,
keysImage: <KeyboardKeys keys={['Ctrl', 'Alt', 'T']} />,
},
darwin: {
text: t.footer.toOpenTerminal,
keysImage: <KeyboardKeys keys={['cmd', 'T']} />,
},
windows_nt: {
text: t.footer.toOpenCommandPrompt,
keysImage: <KeyboardKeys keys={['win', 'R']} />,
},
}

/**
* Footer component.
*
* The component render instructions how to open terminal on the host machine.
* It supports only 'linux', 'windows (win32)' and 'macos (darwin)'.
* If any other platform detected, then the text is not displayed.
*/
const Footer = () => {
const [osType, setOSType] = useState<
'linux' | 'windows_nt' | 'darwin' | null | undefined
>(undefined)

const textAnim = useSpring({
opacity: osType === undefined ? 0 : 1,
})

useEffect(() => {
checkPlatform()
}, [])

const checkPlatform = async () => {
try {
const detectedPlatform = await os.type()

if (
['linux', 'windows_nt', 'darwin'].includes(
detectedPlatform.toLowerCase(),
)
) {
setOSType(
detectedPlatform.toLowerCase() as 'linux' | 'windows_nt' | 'darwin',
)
return
}

setOSType(null)
} catch (_err) {
setOSType(null)
}
}

return (
<StyledFooter data-testid='footer-cmp'>
<p>Press cmd + T to open terminal window</p>
<FooterTextWrapper
style={{
...textAnim,
}}
>
{osType ? (
<Text
type='smallMedium'
color='inherit'
as={'span'}
testId='terminal-instructions-in-footer'
>
{t.footer.press} {TerminalInstructions[osType]?.keysImage}{' '}
{TerminalInstructions[osType]?.text}
</Text>
) : null}
</FooterTextWrapper>
</StyledFooter>
)
}
Expand Down
4 changes: 3 additions & 1 deletion applications/launchpad_v2/src/components/Footer/styles.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { animated } from 'react-spring'
import styled from 'styled-components'

export const StyledFooter = styled.footer`
Expand All @@ -7,6 +8,7 @@ export const StyledFooter = styled.footer`
align-items: center;
`

export const FooterCommand = styled.span`
export const FooterTextWrapper = styled(animated.div)`
text-align: center;
color: ${({ theme }) => theme.tertiary};
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { render, screen } from '@testing-library/react'

import KeyboardKeys from '.'

describe('KeyboardKeys', () => {
it('should render given keys', async () => {
render(<KeyboardKeys keys={['Ctrl', 'R', 'win']} />)

const ctrlTile = screen.getByText('Ctrl')
expect(ctrlTile).toBeInTheDocument()

const rTile = screen.getByText('R')
expect(rTile).toBeInTheDocument()

const winTile = screen.getByTestId('svg-win-key')
expect(winTile).toBeInTheDocument()
})
})
40 changes: 40 additions & 0 deletions applications/launchpad_v2/src/components/KeyboardKeys/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { IconsWrapper, KeyTile, LetterKey } from './styles'
import { KeyboardKeysProps } from './types'
import SvgCmdKey from '../../styles/Icons/CmdKey'
import SvgWinKey from '../../styles/Icons/WinKey'

/**
* Renders keyboard keys as set of tiles.
* Use whenever you need to show the keyboard shortcuts, ie: "Ctrl + Alt + T"
*
* Use 'win' and 'cmd' to render Windows and Command keys.
*
* @param {string[]} keys - the set of keyboard keys
*
* @example
* <KeyboardKeys keys={['Ctrl', 'Alt', 'win']} />
*/
const KeyboardKeys = ({ keys }: KeyboardKeysProps) => {
const result = []

keys.forEach((key, idx) => {
let symbol
switch (key) {
case 'win':
symbol = <SvgWinKey />
break
case 'cmd':
symbol = <SvgCmdKey />
break
default:
symbol = <LetterKey>{key}</LetterKey>
break
}

result.push(<KeyTile key={`keyboard-key-${idx}`}>{symbol}</KeyTile>)
})

return <IconsWrapper>{result}</IconsWrapper>
}

export default KeyboardKeys
31 changes: 31 additions & 0 deletions applications/launchpad_v2/src/components/KeyboardKeys/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import styled from 'styled-components'

export const IconsWrapper = styled.div`
display: inline-block;
vertical-align: baseline;
`

export const KeyTile = styled.span`
display: inline-block;
vertical-align: middle;
text-align: center;
font-size: 10px;
line-height: 10px;
padding: 2px;
background: transparent;
border: 1px solid ${({ theme }) => theme.borderColorLight};
border-radius: 4px;
min-width: 16px;
height: 16px;
box-sizing: border-box;
margin-left: 1px;
margin-right: 1px;
margin-top: -4%;
`

export const LetterKey = styled.span`
text-align: center;
font-size: 12px;
line-height: 12px;
font-weight: 500;
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface KeyboardKeysProps {
keys: string[]
}
Loading

0 comments on commit a39f092

Please sign in to comment.