Browse Source
* Inject services with useContext * Extract service for video settings * Create mock factories for services * Create test data for chat history * Add story to visualize different layouts * Fix renaming mistake * Add landscape and portrait viewports * Add landscape stories --------- Co-authored-by: Gabe Kangas <gabek@real-ity.com>pull/2748/head
14 changed files with 428 additions and 25 deletions
@ -0,0 +1,158 @@
@@ -0,0 +1,158 @@
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react'; |
||||
import { MutableSnapshot, RecoilRoot } from 'recoil'; |
||||
import { makeEmptyClientConfig } from '../../../interfaces/client-config.model'; |
||||
import { ServerStatus, makeEmptyServerStatus } from '../../../interfaces/server-status.model'; |
||||
import { |
||||
accessTokenAtom, |
||||
appStateAtom, |
||||
chatMessagesAtom, |
||||
chatVisibleToggleAtom, |
||||
clientConfigStateAtom, |
||||
currentUserAtom, |
||||
fatalErrorStateAtom, |
||||
isMobileAtom, |
||||
isVideoPlayingAtom, |
||||
serverStatusState, |
||||
} from '../../stores/ClientConfigStore'; |
||||
import { Main } from './Main'; |
||||
import { ClientConfigServiceContext } from '../../../services/client-config-service'; |
||||
import { ChatServiceContext } from '../../../services/chat-service'; |
||||
import { |
||||
ServerStatusServiceContext, |
||||
ServerStatusStaticService, |
||||
} from '../../../services/status-service'; |
||||
import { clientConfigServiceMockOf } from '../../../services/client-config-service.mock'; |
||||
import chatServiceMockOf from '../../../services/chat-service.mock'; |
||||
import serverStatusServiceMockOf from '../../../services/status-service.mock'; |
||||
import { VideoSettingsServiceContext } from '../../../services/video-settings-service'; |
||||
import videoSettingsServiceMockOf from '../../../services/video-settings-service.mock'; |
||||
import { grootUser, spidermanUser } from '../../../interfaces/user.fixture'; |
||||
import { exampleChatHistory } from '../../../interfaces/chat-message.fixture'; |
||||
|
||||
export default { |
||||
title: 'owncast/Layout/Main', |
||||
parameters: { |
||||
layout: 'fullscreen', |
||||
}, |
||||
} satisfies ComponentMeta<typeof Main>; |
||||
|
||||
type StateInitializer = (mutableState: MutableSnapshot) => void; |
||||
|
||||
const composeStateInitializers = |
||||
(...fns: Array<StateInitializer>): StateInitializer => |
||||
state => |
||||
fns.forEach(fn => fn?.(state)); |
||||
|
||||
const defaultClientConfig = { |
||||
...makeEmptyClientConfig(), |
||||
logo: 'http://localhost:8080/logo', |
||||
name: "Spiderman's super serious stream", |
||||
summary: 'Strong Spidey stops supervillains! Streamed Saturdays & Sundays.', |
||||
extraPageContent: '<marquee>Spiderman is cool</marquee>', |
||||
}; |
||||
|
||||
const defaultServerStatus = makeEmptyServerStatus(); |
||||
const onlineServerStatus: ServerStatus = { |
||||
...defaultServerStatus, |
||||
online: true, |
||||
viewerCount: 5, |
||||
}; |
||||
|
||||
const initializeDefaultState = (mutableState: MutableSnapshot) => { |
||||
mutableState.set(appStateAtom, { |
||||
videoAvailable: false, |
||||
chatAvailable: false, |
||||
}); |
||||
mutableState.set(clientConfigStateAtom, defaultClientConfig); |
||||
mutableState.set(chatVisibleToggleAtom, true); |
||||
mutableState.set(accessTokenAtom, 'token'); |
||||
mutableState.set(currentUserAtom, { |
||||
...spidermanUser, |
||||
isModerator: false, |
||||
}); |
||||
mutableState.set(serverStatusState, defaultServerStatus); |
||||
mutableState.set(isMobileAtom, false); |
||||
|
||||
mutableState.set(chatMessagesAtom, exampleChatHistory); |
||||
mutableState.set(isVideoPlayingAtom, false); |
||||
mutableState.set(fatalErrorStateAtom, null); |
||||
}; |
||||
|
||||
const ClientConfigServiceMock = clientConfigServiceMockOf(defaultClientConfig); |
||||
const ChatServiceMock = chatServiceMockOf(exampleChatHistory, { |
||||
...grootUser, |
||||
accessToken: 'some fake token', |
||||
}); |
||||
const DefaultServerStatusServiceMock = serverStatusServiceMockOf(defaultServerStatus); |
||||
const OnlineServerStatusServiceMock = serverStatusServiceMockOf(onlineServerStatus); |
||||
const VideoSettingsServiceMock = videoSettingsServiceMockOf([]); |
||||
|
||||
const Template: ComponentStory<typeof Main> = ({ |
||||
initializeState, |
||||
ServerStatusServiceMock = DefaultServerStatusServiceMock, |
||||
...args |
||||
}: { |
||||
initializeState: (mutableState: MutableSnapshot) => void; |
||||
ServerStatusServiceMock: ServerStatusStaticService; |
||||
}) => ( |
||||
<RecoilRoot initializeState={composeStateInitializers(initializeDefaultState, initializeState)}> |
||||
<ClientConfigServiceContext.Provider value={ClientConfigServiceMock}> |
||||
<ChatServiceContext.Provider value={ChatServiceMock}> |
||||
<ServerStatusServiceContext.Provider value={ServerStatusServiceMock}> |
||||
<VideoSettingsServiceContext.Provider value={VideoSettingsServiceMock}> |
||||
<Main {...args} /> |
||||
</VideoSettingsServiceContext.Provider> |
||||
</ServerStatusServiceContext.Provider> |
||||
</ChatServiceContext.Provider> |
||||
</ClientConfigServiceContext.Provider> |
||||
</RecoilRoot> |
||||
); |
||||
|
||||
export const OfflineDesktop: typeof Template = Template.bind({}); |
||||
|
||||
export const OfflineMobile: typeof Template = Template.bind({}); |
||||
OfflineMobile.args = { |
||||
initializeState: (mutableState: MutableSnapshot) => { |
||||
mutableState.set(isMobileAtom, true); |
||||
}, |
||||
}; |
||||
OfflineMobile.parameters = { |
||||
viewport: { |
||||
defaultViewport: 'mobile1', |
||||
}, |
||||
}; |
||||
|
||||
export const OfflineTablet: typeof Template = Template.bind({}); |
||||
OfflineTablet.parameters = { |
||||
viewport: { |
||||
defaultViewport: 'tablet', |
||||
}, |
||||
}; |
||||
|
||||
export const Online: typeof Template = Template.bind({}); |
||||
Online.args = { |
||||
ServerStatusServiceMock: OnlineServerStatusServiceMock, |
||||
}; |
||||
|
||||
export const OnlineMobile: typeof Template = Online.bind({}); |
||||
OnlineMobile.args = { |
||||
ServerStatusServiceMock: OnlineServerStatusServiceMock, |
||||
initializeState: (mutableState: MutableSnapshot) => { |
||||
mutableState.set(isMobileAtom, true); |
||||
}, |
||||
}; |
||||
OnlineMobile.parameters = { |
||||
viewport: { |
||||
defaultViewport: 'mobile1', |
||||
}, |
||||
}; |
||||
|
||||
export const OnlineTablet: typeof Template = Online.bind({}); |
||||
OnlineTablet.args = { |
||||
ServerStatusServiceMock: OnlineServerStatusServiceMock, |
||||
}; |
||||
OnlineTablet.parameters = { |
||||
viewport: { |
||||
defaultViewport: 'tablet', |
||||
}, |
||||
}; |
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
import { ChatMessage } from './chat-message.model'; |
||||
import { MessageType } from './socket-events'; |
||||
import { spidermanUser, grootUser } from './user.fixture'; |
||||
import { User } from './user.model'; |
||||
|
||||
export const createMessages = ( |
||||
basicMessages: Array<{ body: string; user: User }>, |
||||
): Array<ChatMessage> => { |
||||
const baseDate = new Date(2022, 1, 3).valueOf(); |
||||
return basicMessages.map( |
||||
({ body, user }, index): ChatMessage => ({ |
||||
body, |
||||
user, |
||||
id: index.toString(), |
||||
type: MessageType.CHAT, |
||||
timestamp: new Date(baseDate + 1_000 * index), |
||||
}), |
||||
); |
||||
}; |
||||
|
||||
export const exampleChatHistory = createMessages([ |
||||
{ |
||||
body: 'So, how do you like my new suit?', |
||||
user: spidermanUser, |
||||
}, |
||||
{ |
||||
body: 'Im am Groot.', |
||||
user: grootUser, |
||||
}, |
||||
{ |
||||
body: 'Really? That bad?', |
||||
user: spidermanUser, |
||||
}, |
||||
{ |
||||
body: 'Im am Groot!', |
||||
user: grootUser, |
||||
}, |
||||
{ |
||||
body: 'But what about the new web slingers?', |
||||
user: spidermanUser, |
||||
}, |
||||
{ |
||||
body: 'Im am Groooooooooooooooot.', |
||||
user: grootUser, |
||||
}, |
||||
{ |
||||
body: "Ugh, come on, they aren't THAT big!", |
||||
user: spidermanUser, |
||||
}, |
||||
{ |
||||
body: 'I am Groot.', |
||||
user: grootUser, |
||||
}, |
||||
{ |
||||
body: "Fine then. I don't like your new leaves either!", |
||||
user: spidermanUser, |
||||
}, |
||||
{ |
||||
body: 'I AM GROOT!!!!!', |
||||
user: grootUser, |
||||
}, |
||||
]); |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
import { User } from './user.model'; |
||||
|
||||
export const createUser = (name: string, color: number, createdAt: Date): User => ({ |
||||
id: name, |
||||
displayName: name, |
||||
displayColor: color, |
||||
createdAt, |
||||
authenticated: true, |
||||
nameChangedAt: createdAt, |
||||
previousNames: [], |
||||
scopes: [], |
||||
}); |
||||
|
||||
export const spidermanUser = createUser('Spiderman', 1, new Date(2020, 1, 2)); |
||||
export const grootUser = createUser('Groot', 1, new Date(2020, 2, 3)); |
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
import { ChatMessage } from '../interfaces/chat-message.model'; |
||||
import { ChatStaticService, UserRegistrationResponse } from './chat-service'; |
||||
|
||||
export const chatServiceMockOf = ( |
||||
chatHistory: ChatMessage[], |
||||
userRegistrationResponse: UserRegistrationResponse, |
||||
): ChatStaticService => |
||||
class ChatServiceMock { |
||||
public static async getChatHistory(): Promise<ChatMessage[]> { |
||||
return chatHistory; |
||||
} |
||||
|
||||
public static async registerUser(): Promise<UserRegistrationResponse> { |
||||
return userRegistrationResponse; |
||||
} |
||||
}; |
||||
|
||||
export default chatServiceMockOf; |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
import { ClientConfig } from '../interfaces/client-config.model'; |
||||
import { ClientConfigStaticService } from './client-config-service'; |
||||
|
||||
export const clientConfigServiceMockOf = (config: ClientConfig): ClientConfigStaticService => |
||||
class ClientConfigServiceMock { |
||||
public static async getConfig(): Promise<ClientConfig> { |
||||
return config; |
||||
} |
||||
}; |
||||
|
||||
export default clientConfigServiceMockOf; |
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
import { ServerStatus } from '../interfaces/server-status.model'; |
||||
import { ServerStatusStaticService } from './status-service'; |
||||
|
||||
export const serverStatusServiceMockOf = ( |
||||
serverStatus: ServerStatus, |
||||
): ServerStatusStaticService => |
||||
class ServerStatusServiceMock { |
||||
public static async getStatus(): Promise<ServerStatus> { |
||||
return serverStatus; |
||||
} |
||||
}; |
||||
|
||||
export default serverStatusServiceMockOf; |
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
import { VideoSettingsStaticService, VideoQuality } from './video-settings-service'; |
||||
|
||||
export const videoSettingsServiceMockOf = ( |
||||
videoQualities: Array<VideoQuality>, |
||||
): VideoSettingsStaticService => |
||||
class VideoSettingsServiceMock { |
||||
public static async getVideoQualities(): Promise<Array<VideoQuality>> { |
||||
return videoQualities; |
||||
} |
||||
}; |
||||
|
||||
export default videoSettingsServiceMockOf; |
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
import { createContext } from 'react'; |
||||
|
||||
export type VideoQuality = { |
||||
index: number; |
||||
/** |
||||
* This property is not just for display or so |
||||
* but it holds information |
||||
* |
||||
* @example '1.2Mbps@24fps' |
||||
*/ |
||||
name: string; |
||||
}; |
||||
|
||||
export interface VideoSettingsStaticService { |
||||
getVideoQualities(): Promise<Array<VideoQuality>>; |
||||
} |
||||
|
||||
class VideoSettingsService { |
||||
private static readonly VIDEO_CONFIG_URL = '/api/video/variants'; |
||||
|
||||
public static async getVideoQualities(): Promise<Array<VideoQuality>> { |
||||
let qualities: Array<VideoQuality> = []; |
||||
|
||||
try { |
||||
const response = await fetch(VideoSettingsService.VIDEO_CONFIG_URL); |
||||
qualities = await response.json(); |
||||
console.log(qualities); |
||||
} catch (e) { |
||||
console.error(e); |
||||
} |
||||
return qualities; |
||||
} |
||||
} |
||||
|
||||
export const VideoSettingsServiceContext = |
||||
createContext<VideoSettingsStaticService>(VideoSettingsService); |
Loading…
Reference in new issue