Skip to content

Commit

Permalink
Stories are loaded the same as Storybooks (#7)
Browse files Browse the repository at this point in the history
# Problem

The `loadStorybookModule` function works a bit differently from
`loadStory` in that it will throw on any issues. `loadStory` should do
the same for consistency

# Solution

I've updated the API to remove `loadStory` in favor of `loadStoryModule`
which functions the same as `loadStorybookModule`

I also was given a vision that made it clear how to handle Hoarcekat
stories, so those work now in our e2e suite too
  • Loading branch information
vocksel authored Oct 31, 2024
1 parent 714078c commit 290c65d
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 98 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
"luau-lsp.require.directoryAliases": {
"@pkg": "./Packages",
"@root": "./src",
"@lune/": "~/.lune/.typedefs/0.8.7"
"@lune/": "~/.lune/.typedefs/0.8.7/"
}
}
2 changes: 1 addition & 1 deletion rokit.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ rojo = "rojo-rbx/[email protected]"
selene = "Kampfkarren/[email protected]"
StyLua = "JohnnyMorganz/[email protected]"
wally = "UpliftGames/[email protected]"
luau-lsp = "JohnnyMorganz/luau-lsp@1.33.1"
luau-lsp = "JohnnyMorganz/luau-lsp@1.34.0"
wally-package-types = "JohnnyMorganz/[email protected]"
lune = "lune-org/[email protected]"
darklua = "seaofvoices/[email protected]"
Expand Down
11 changes: 3 additions & 8 deletions src/e2e/e2e.spec.luau
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ describeEach(storybookModules)("e2e %s", function(storybookModule)
assert(#storyModules >= 2, "storybook must have at least 2 stories")

for _, storyModule in storyModules do
local story, err = loadStoryModule(mockModuleLoader, storyModule, storybook)
assert(story, `bad story: {err}`)
local story = loadStoryModule(mockModuleLoader, storyModule, storybook)

if story.controls then
numStoriesWithControls += 1
Expand All @@ -78,11 +77,8 @@ describeEach(storybookModules)("e2e %s", function(storybookModule)

describeEach(storyModules)("%s", function(storyModule)
test("basic mount/unmount lifecycle", function()
local story, err = loadStoryModule(mockModuleLoader, storyModule, storybook)
assert(story, `bad story: {err}`)

local story = loadStoryModule(mockModuleLoader, storyModule, storybook)
local renderer = createRendererForStory(story)

local lifecycle = render(renderer, container, story)

afterRender()
Expand All @@ -95,8 +91,7 @@ describeEach(storybookModules)("e2e %s", function(storybookModule)
end)

test("rerenders on control changes", function()
local story, err = loadStoryModule(mockModuleLoader, storyModule, storybook)
assert(story, `bad story: {err}`)
local story = loadStoryModule(mockModuleLoader, storyModule, storybook)

if story.controls then
local renderer = createRendererForStory(story)
Expand Down
8 changes: 5 additions & 3 deletions src/hooks/useStory.luau
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ local function useStory(
})

local loadStory = React.useCallback(function()
local story, err = loadStoryModule(loader, module, storybook)
local success, result = pcall(function()
loadStoryModule(loader, module, storybook)
end)

setState({
story = story,
err = err,
story = if success then result else nil,
err = if success then nil else result,
})
end, { loader, module, storybook } :: { unknown })

Expand Down
44 changes: 1 addition & 43 deletions src/init.luau
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
local ModuleLoader = require("@pkg/ModuleLoader")
local types = require("./types")

export type Story<T> = types.Story<T>
Expand All @@ -7,45 +6,7 @@ export type Storybook = types.Storybook
export type StoryProps = types.StoryProps
export type StoryRenderer<T> = types.StoryRenderer<T>

type API = {
isStorybookModule: (instance: Instance) -> boolean,
isStoryModule: (instance: Instance) -> boolean,

-- loadStorybooks: (loader: ModuleLoader.ModuleLoader, parent: Instance) -> { Storybook },
loadStory: <T>(
loader: ModuleLoader.ModuleLoader,
module: ModuleScript,
storybook: Storybook
) -> (Story<T>?, string?),

render: <T>(
renderer: StoryRenderer<T>,
container: Instance,
story: Story<T>,
initialControls: StoryControls?
) -> {
update: (newControls: StoryControls?) -> (),
unmount: () -> (),
},

createRendererForStory: <T>(story: Story<T>) -> StoryRenderer<T>,

useStorybooks: (parent: Instance, loader: ModuleLoader.ModuleLoader) -> { Storybook },
useStory: (
module: ModuleScript,
storybook: Storybook,
loader: ModuleLoader.ModuleLoader
) -> (Story<unknown>?, string?),

createFusionRenderer: ({ Fusion: unknown }) -> StoryRenderer<Instance>,
createReactRenderer: ({ React: unknown, ReactRoblox: unknown }) -> StoryRenderer<unknown>,
createRoactRenderer: ({ Roact: unknown }) -> StoryRenderer<unknown>,
createRobloxRenderer: () -> StoryRenderer<Instance>,
-- createDeveloperStorybookRenderer: () -> StoryRenderer<unknown>,
-- createHoarcekatRenderer: () -> StoryRenderer<unknown>,
}

local api: API = {
return {
-- Validation
isStorybookModule = require("./isStoryModule"),
isStoryModule = require("./isStoryModule"),
Expand All @@ -55,7 +16,6 @@ local api: API = {
findStoryModulesForStorybook = require("./findStoryModulesForStorybook"),

-- Module loading
loadStory = require("./loadStoryModule"),
loadStoryModule = require("./loadStoryModule"),
loadStorybookModule = require("./loadStorybookModule"),

Expand All @@ -71,5 +31,3 @@ local api: API = {
useStory = require("./hooks/useStory"),
useStorybooks = require("./hooks/useStorybooks"),
}

return api
54 changes: 25 additions & 29 deletions src/loadStoryModule.luau
Original file line number Diff line number Diff line change
@@ -1,53 +1,49 @@
local ModuleLoader = require("@pkg/ModuleLoader")
local Sift = require("@pkg/Sift")

local constants = require("@root/constants")
local types = require("@root/types")

local Errors = {
MalformedStory = "Story is malformed. Check the source of %q and make sure its properties are correct",
Generic = "Failed to load story %q. Error: %s",
}
type Storybook = types.Storybook
type Story<T> = types.Story<T>

local function loadStoryModule<T>(
loader: ModuleLoader.ModuleLoader,
storyModule: ModuleScript,
storybook: types.Storybook
): (types.Story<T>?, string?)
storybook: Storybook
): Story<T>
local success, result = pcall(function()
return loader:require(storyModule)
end)

if not success then
return nil, Errors.Generic:format(storyModule:GetFullName(), tostring(result))
error(`Failed to load story {storyModule:GetFullName()}. Error: {result})`)
end

local story: types.Story<T>
-- Support for Hoarcekat stories
if typeof(result) == "function" then
story = {
name = storyModule.Name,
story = result,
storybook = storybook,
source = storyModule,
local callback = result
result = {
story = function(props)
return callback(props.container, props)
end,
}
else
local isValid, message = types.IStory(result)

if isValid then
story = Sift.Dictionary.merge({
name = storyModule.Name,
storybook = storybook,
source = storyModule,
}, result)
else
return nil, Errors.Generic:format(storyModule:GetFullName(), message)
end
end

if story then
return story, nil
else
return nil, Errors.MalformedStory:format(storyModule:GetFullName())
local isValid, message = types.IStory(result)

if not isValid then
error(
`Story is malformed. Check the source of {storyModule:GetFullName()} and make sure its properties are `
.. `correct. Error: {message}`
)
end

return Sift.Dictionary.merge({
name = storyModule.Name:gsub(constants.STORY_NAME_PATTERN, ""),
storybook = storybook,
source = storyModule,
}, result)
end

return loadStoryModule
24 changes: 11 additions & 13 deletions src/loadStoryModule.spec.luau
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ test("use the name of the story module for the story name", function()
local story = loadStoryModule(loader, storyModule, MOCK_PLAIN_STORYBOOK)

assert(story ~= nil, "story not defined")
expect(story.name).toEqual(storyModule.Name)
expect(story.name).toEqual("SampleName")
end)

test("pass the storybook's renderer to the story", function()
Expand All @@ -100,28 +100,27 @@ test("pass the storybook's renderer to the story", function()
}
]])

local story, err = loadStoryModule(loader, storyModule, MOCK_REACT_STORYBOOK)
local story = loadStoryModule(loader, storyModule, MOCK_REACT_STORYBOOK)

expect(story).toBeDefined()
expect(err).toBeNil()

story, err = loadStoryModule(loader, storyModule, MOCK_ROACT_STORYBOOK)
story = loadStoryModule(loader, storyModule, MOCK_ROACT_STORYBOOK)

expect(story).toBeDefined()
expect(err).toBeNil()
end)

test("generic failures for stories", function()
local loader = ModuleLoader.new()
local storyModule = createMockStoryModule([[
syntax error
return {
}
]])

local story, err = loadStoryModule(loader, storyModule, MOCK_PLAIN_STORYBOOK)

expect(story).toBeNil()
expect(err).toBeDefined()
expect(function()
loadStoryModule(loader, storyModule, MOCK_PLAIN_STORYBOOK)
end).toThrow("Failed to load story")
end)

test("malformed stories", function()
Expand All @@ -133,8 +132,7 @@ test("malformed stories", function()
}
]])

local story, err = loadStoryModule(loader, storyModule, MOCK_PLAIN_STORYBOOK)

expect(story).toBeNil()
expect(err).toBeDefined()
expect(function()
loadStoryModule(loader, storyModule, MOCK_PLAIN_STORYBOOK)
end).toThrow("Story is malformed")
end)

0 comments on commit 290c65d

Please sign in to comment.