Skip to content
Draft
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
101 changes: 101 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import type { StorybookConfig } from "@storybook/nextjs";
import path from "path";

const config: StorybookConfig = {
framework: {
name: "@storybook/nextjs",
options: {}
},
stories: [
"../components/**/*.stories.@(js|jsx|ts|tsx)",
"../stories/**/*.stories.@(js|jsx|ts|tsx)"
],
addons: [
"@storybook/addon-essentials",
"@storybook/addon-interactions"
],
staticDirs: [
{ from: "../public", to: "/" }
],
webpackFinal: async (config) => {
if (!config.resolve) config.resolve = {};
if (!config.resolve.alias) config.resolve.alias = {} as any;

// Base alias for project root
(config.resolve.alias as any)["@"] = path.resolve(__dirname, "..");

// Alias Next.js app router navigation to Storybook-friendly mocks
config.resolve.alias["next/navigation"] = path.resolve(
__dirname,
"mocks/next-navigation.ts"
);

// Alias hooks to mocked implementations for isolated rendering
config.resolve.alias["@/hooks/useAssignment"] = path.resolve(
__dirname,
"mocks/hooks/useAssignment.tsx"
);
config.resolve.alias["@/hooks/useSubmission"] = path.resolve(
__dirname,
"mocks/hooks/useSubmission.tsx"
);
config.resolve.alias["@/hooks/useSubmission.tsx"] = path.resolve(
__dirname,
"mocks/hooks/useSubmission.tsx"
);
config.resolve.alias["@/hooks/useSubmissionReview"] = path.resolve(
__dirname,
"mocks/hooks/useSubmissionReview.tsx"
);
config.resolve.alias["@/hooks/useClassProfiles"] = path.resolve(
__dirname,
"mocks/hooks/useClassProfiles.tsx"
);
config.resolve.alias["@/hooks/useRubricVisibility"] = path.resolve(
__dirname,
"mocks/hooks/useRubricVisibility.ts"
);
config.resolve.alias["@/hooks/useCourseController"] = path.resolve(
__dirname,
"mocks/hooks/useCourseController.tsx"
);
config.resolve.alias[path.resolve(__dirname, "../hooks/useCourseController.tsx")] = path.resolve(
__dirname,
"mocks/hooks/useCourseController.tsx"
);

// Mock refine core hooks used across components
config.resolve.alias["@refinedev/core"] = path.resolve(
__dirname,
"mocks/refine-core.ts"
);

// Mock Supabase client used by components like artifact viewers
config.resolve.alias["@/utils/supabase/client"] = path.resolve(
__dirname,
"mocks/utils-supabase-client.ts"
);

// Hard override absolute source files in case path alias resolution bypasses module name
config.resolve.alias[path.resolve(__dirname, "../hooks/useSubmission.tsx")] = path.resolve(
__dirname,
"mocks/hooks/useSubmission.tsx"
);
config.resolve.alias[path.resolve(__dirname, "../hooks/useSubmission")] = path.resolve(
__dirname,
"mocks/hooks/useSubmission.tsx"
);
config.resolve.alias[path.resolve(__dirname, "../hooks/useSubmissionReview.tsx")] = path.resolve(
__dirname,
"mocks/hooks/useSubmissionReview.tsx"
);
config.resolve.alias[path.resolve(__dirname, "../hooks/useAssignment.tsx")] = path.resolve(
__dirname,
"mocks/hooks/useAssignment.tsx"
);

return config;
}
};

export default config;
106 changes: 106 additions & 0 deletions .storybook/mocks/hooks/useAssignment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React, { createContext, useContext } from "react";
import type { HydratedRubric, HydratedRubricCriteria, HydratedRubricPart } from "../../../../utils/supabase/DatabaseTypes";

const sampleRubric: HydratedRubric = {
id: 1,
assignment_id: 1,
name: "Grading Rubric",
description: "Sample rubric for Storybook",
review_round: "grading-review" as any,
rubric_parts: [
{
id: 10,
rubric_id: 1,
name: "Code Quality",
description: "",
ordinal: 1,
rubric_criteria: [
{
id: 100,
rubric_part_id: 10,
rubric_id: 1,
name: "Style",
description: "Follows style guide",
ordinal: 1,
is_additive: true,
total_points: 10,
min_checks_per_submission: null,
max_checks_per_submission: null,
rubric_checks: [
{
id: 1000,
rubric_criteria_id: 100,
name: "Good naming",
description: "Variables and functions are well named",
points: 2,
is_annotation: false,
student_visibility: "always" as any
} as any,
{
id: 1001,
rubric_criteria_id: 100,
name: "Inline comments",
description: "Helpful inline comments",
points: 1,
is_annotation: true,
annotation_target: "file" as any,
student_visibility: "if_released" as any
} as any
]
} as HydratedRubricCriteria
]
} as HydratedRubricPart
]
} as HydratedRubric;

const Ctx = createContext({
assignment: {
id: 1,
class_id: 1,
grading_rubric_id: 1,
total_points: 100,
autograder_points: 50
},
rubrics: [sampleRubric],
rubricCheckById: new Map<number, any>([
[1000, sampleRubric.rubric_parts[0].rubric_criteria[0].rubric_checks[0]],
[1001, sampleRubric.rubric_parts[0].rubric_criteria[0].rubric_checks[1]]
]),
rubricCriteriaById: new Map<number, any>([[100, sampleRubric.rubric_parts[0].rubric_criteria[0]]]),
reviewAssignments: { rows: [] },
regradeRequests: { rows: [] },
submissions: { rows: [] },
assignmentGroups: { rows: [] },
isReady: true,
getReviewAssignmentRubricPartsController: () => ({ list: (cb: any) => ({ unsubscribe: () => {}, data: [] }) }),
releaseReviewAssignmentRubricPartsController: (_id: number) => {}
});

export function AssignmentProvider({ children }: { children: React.ReactNode }) {
return <Ctx.Provider value={useContext(Ctx)}>{children}</Ctx.Provider>;
}

export function useAssignmentController() {
return useContext(Ctx) as any;
}

export function useRubrics() {
return useContext(Ctx).rubrics as HydratedRubric[];
}

export function useRubricById(id?: number | null) {
if (!id) return undefined;
return useRubrics().find((r) => r.id === id);
}

export function useReviewAssignmentRubricParts() {
return [] as any[];
}

export function useMyReviewAssignments() {
return [] as any[];
}

export function useReviewAssignment() {
return undefined as any;
}
18 changes: 18 additions & 0 deletions .storybook/mocks/hooks/useClassProfiles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export function useClassProfiles() {
return {
private_profile_id: "profile_1",
role: { role: "instructor" as const }
};
}

export function useIsGraderOrInstructor() {
return true;
}

export function useIsInstructor() {
return true;
}

export function useIsStudent() {
return false;
}
21 changes: 21 additions & 0 deletions .storybook/mocks/hooks/useCourseController.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export function useCourseController() {
return {
classRealTimeController: {
subscribe: () => () => {},
unsubscribe: () => {}
},
time_zone: "America/New_York"
} as const;
}

export function useCourse() {
return { time_zone: "America/New_York" } as const;
}

export function useAssignmentDueDate() {
return { dueDate: null, hoursExtended: 0, time_zone: "America/New_York" } as const;
}

export function useIsDroppedStudent() {
return false;
}
13 changes: 13 additions & 0 deletions .storybook/mocks/hooks/useRubricVisibility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { HydratedRubricCheck } from "@/utils/supabase/DatabaseTypes";

export function useShouldShowRubricCheck({
check
}: {
check: HydratedRubricCheck;
rubricCheckComments?: unknown[];
reviewForThisRubric?: unknown;
isGrader?: boolean;
isPreviewMode?: boolean;
}) {
return true;
}
115 changes: 115 additions & 0 deletions .storybook/mocks/hooks/useSubmission.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React, { createContext, useContext } from "react";
import type { SubmissionWithFilesGraderResultsOutputTestsAndRubric } from "../../../../utils/supabase/DatabaseTypes";

const sampleSubmission: SubmissionWithFilesGraderResultsOutputTestsAndRubric = {
id: 1,
class_id: 1,
assignment_id: 1,
profile_id: "profile_1",
assignment_group_id: null,
ordinal: 1,
is_active: true,
is_not_graded: false,
repository: "org/repo",
sha: "abcdef1",
released: new Date().toISOString(),
grading_review_id: 1,
assignments: {
id: 1,
class_id: 1,
total_points: 100,
autograder_points: 50,
title: "Assignment 1",
grading_rubric_id: 1,
rubrics: {
id: 1,
name: "Grading Rubric",
rubric_criteria: []
} as any
} as any,
submission_files: [
{ id: 11, class_id: 1, submission_id: 1, name: "Main.java", contents: "public class Main{}" } as any
],
submission_artifacts: [],
grader_results: { score: 40, max_score: 50, grader_result_tests: [], grader_result_output: [] } as any
};

type CtxType = {
submissionController: any;
};
const defaultValue: CtxType = {
submissionController: {
submission: sampleSubmission,
submission_comments: {
list: (cb: any) => ({ unsubscribe: () => {}, data: [] }),
getById: () => ({ data: undefined }),
update: async () => ({}),
create: async () => ({})
},
submission_file_comments: {
list: (cb: any) => ({ unsubscribe: () => {}, data: [] }),
getById: () => ({ data: undefined }),
update: async () => ({}),
create: async () => ({}),
delete: async () => ({})
},
submission_artifact_comments: {
list: (cb: any) => ({ unsubscribe: () => {}, data: [] }),
getById: () => ({ data: undefined }),
update: async () => ({}),
create: async () => ({}),
delete: async () => ({})
},
submission_reviews: {
rows: [{ id: 1, rubric_id: 1, released: true }],
list: (cb: any) => ({ unsubscribe: () => {}, data: [{ id: 1, rubric_id: 1, released: true }] }),
getById: (_id: number) => ({ data: { id: 1, rubric_id: 1, released: true } }),
update: async () => ({})
}
}
};
const Ctx = createContext<CtxType>(defaultValue);

export function SubmissionProvider({ children }: { children: React.ReactNode; submission_id?: number }) {
return <Ctx.Provider value={defaultValue}>{children}</Ctx.Provider>;
}

export function useSubmission() {
const ctx = useContext(Ctx);
if (!ctx) throw new Error("SubmissionContext not found");
return ctx.submissionController.submission as any;
}

export function useSubmissionMaybe() {
return useSubmission();
}

export function useSubmissionController() {
const ctx = useContext(Ctx);
if (!ctx) throw new Error("SubmissionContext not found");
return ctx.submissionController as any;
}

export function useSubmissionFileComments() {
return [] as any[];
}

export function useSubmissionComments() {
return [] as any[];
}

export function useSubmissionArtifactComments() {
return [] as any[];
}

export function useReviewAssignment() {
return { reviewAssignment: undefined, isLoading: false } as any;
}

export function useSubmissionReviewOrGradingReview() {
return { id: 1, released: true, rubric_id: 1 } as any;
}

export function useWritableSubmissionReviews() {
return [{ id: 1, rubric_id: 1 }] as any;
}
Loading
Loading