Skip to content

Commit 73f1fe3

Browse files
authored
Study decompose (#644)
2 parents 36ac599 + 31a88fb commit 73f1fe3

31 files changed

+1230
-738
lines changed

cypress/e2e/course-creation.cy.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
describe('Course Creation', () => {
2+
let firstUsername;
3+
let secondUsername;
4+
const privateCourseName = `Private Test Course ${Math.floor(Math.random() * 10000)}`;
5+
const publicCourseName = `Public Test Course ${Math.floor(Math.random() * 10000)}`;
6+
const courseDescription = 'This is a test course created by automated testing';
7+
8+
before(() => {
9+
// We'll use this hook to clean up any test data if needed
10+
});
11+
12+
it('should allow a user to create private and public courses', () => {
13+
// Register first user
14+
cy.registerUser().then((user) => {
15+
firstUsername = user;
16+
// Wait for registration to complete and redirect
17+
cy.url().should('include', `/u/${firstUsername}/new`);
18+
19+
// Navigate to the quilts (courses) page
20+
cy.visit('/quilts');
21+
22+
// Create a private course
23+
cy.get('[data-cy="create-course-fab"]').click();
24+
cy.get('[data-cy="course-name-input"]').type(privateCourseName);
25+
cy.get('[data-cy="course-description-input"]').type(courseDescription);
26+
cy.get('[data-cy="private-radio"]').click();
27+
cy.get('[data-cy="save-course-button"]').click();
28+
29+
// Wait for creation to complete and dialog to close
30+
cy.wait(1000);
31+
32+
// click the show-more button
33+
cy.get('[data-cy="courses-show-more-button"]').click();
34+
35+
// Verify the private course was created and appears in available courses
36+
cy.contains('h2', 'Available Quilts')
37+
.parent()
38+
.find('[data-cy="course-title"]')
39+
.should('contain', privateCourseName);
40+
41+
// Create a public course
42+
cy.get('[data-cy="create-course-fab"]').click();
43+
cy.get('[data-cy="course-name-input"]').type(publicCourseName);
44+
cy.get('[data-cy="course-description-input"]').type(courseDescription);
45+
cy.get('[data-cy="public-radio"]').click();
46+
cy.get('[data-cy="save-course-button"]').click();
47+
48+
// Wait for creation to complete and dialog to close
49+
cy.wait(1000);
50+
51+
// Verify the public course was created and appears in available courses
52+
cy.contains('h2', 'Available Quilts')
53+
.parent()
54+
.find('[data-cy="course-title"]')
55+
.should('contain', publicCourseName);
56+
});
57+
});
58+
59+
it('should show only public courses to other users', () => {
60+
// Register second user
61+
cy.registerUser().then((user) => {
62+
secondUsername = user;
63+
// Wait for registration to complete and redirect
64+
cy.url().should('include', `/u/${secondUsername}/new`);
65+
66+
// Navigate to the quilts (courses) page
67+
cy.visit('/quilts');
68+
69+
// click the show-more button
70+
cy.get('[data-cy="courses-show-more-button"]').click();
71+
72+
// Look for public course in available courses section
73+
cy.contains('h2', 'Available Quilts')
74+
.parent()
75+
.find('[data-cy="course-title"]')
76+
.should('contain', publicCourseName);
77+
78+
// Verify private course is not visible to this user
79+
cy.contains('h2', 'Available Quilts')
80+
.parent()
81+
.find('[data-cy="course-title"]')
82+
.should('not.contain', privateCourseName);
83+
});
84+
});
85+
});
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// vue-skuilder/cypress/e2e/course-registration.cy.js
2+
describe('Course Registration', () => {
3+
let username;
4+
5+
beforeEach(() => {
6+
// Register a new user before each test
7+
cy.registerUser().then((user) => {
8+
username = user;
9+
// Wait for registration to complete and redirect
10+
cy.url().should('include', `/u/${username}/new`);
11+
});
12+
});
13+
14+
it('should display available courses on the quilts page', () => {
15+
// Navigate to the quilts (courses) page
16+
cy.visit('/quilts');
17+
18+
// Check that the available courses section is visible
19+
cy.contains('h2', 'Available Quilts').should('be.visible');
20+
21+
// Verify that course cards are displayed
22+
cy.get('[data-cy="available-course-card"]').should('have.length.at.least', 1);
23+
});
24+
25+
it('should allow a user to register for a course', () => {
26+
// Navigate to the quilts page
27+
cy.visit('/quilts');
28+
29+
// Get the first available course and store its name
30+
cy.get('[data-cy="available-course-card"]')
31+
.first()
32+
.find('[data-cy="course-title"]')
33+
.invoke('text')
34+
.then((text) => {
35+
let courseName = text.trim();
36+
37+
// Now click the register button
38+
cy.get('[data-cy="available-course-card"]')
39+
.first()
40+
.find('[data-cy="register-course-button"]')
41+
.click();
42+
43+
// Wait a moment for registration to process
44+
cy.wait(1000);
45+
46+
// Verify the course appears in the user's registered courses
47+
cy.get('[data-cy="registered-course"]').should('contain', courseName);
48+
});
49+
});
50+
51+
it('should allow registration using the custom command', () => {
52+
// Register for the first available course
53+
cy.registerForCourse();
54+
55+
// Verify registration by checking the registered courses panel
56+
cy.get('[data-cy="registered-quilts-panel"]').click();
57+
cy.get('[data-cy="registered-course"]').should('have.length.at.least', 1);
58+
});
59+
60+
it('should show registered courses on the study page', () => {
61+
// Register for a course first
62+
cy.registerForCourse();
63+
64+
// Navigate to the study page
65+
cy.visit('/study');
66+
67+
// Check that the registered course appears in the study options
68+
cy.get('[data-cy="select-quilts-header"]').should('be.visible');
69+
cy.get('[data-cy="course-checkbox"]').should('have.length.at.least', 1);
70+
71+
// Check that the start button is available
72+
cy.get('[data-cy="start-studying-button"]').should('be.visible');
73+
});
74+
75+
it('should allow a user to drop a registered course', () => {
76+
// First register for a course
77+
cy.registerForCourse();
78+
79+
// Verify registration completed successfully
80+
cy.get('[data-cy="registered-course"]').should('have.length.at.least', 1);
81+
82+
// Get the name of the registered course for later verification
83+
cy.get('[data-cy="registered-course-title"]')
84+
.first()
85+
.invoke('text')
86+
.then((courseName) => {
87+
// Click the drop button for this course
88+
cy.get('[data-cy="drop-course-button"]').first().click();
89+
90+
// Wait for the drop operation to complete
91+
cy.wait(1000);
92+
93+
// Verify the course is no longer in registered courses
94+
cy.get('[data-cy="registered-course-title"]').should('not.exist');
95+
96+
// Check that the course appears again in available courses
97+
cy.contains('h2', 'Available Quilts')
98+
.parent()
99+
.find('[data-cy="course-title"]')
100+
.should('contain', courseName);
101+
});
102+
});
103+
});

cypress/e2e/user-registration.cy.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,35 @@ describe('User Registration', () => {
8181
cy.contains('Already logged in').should('be.visible');
8282
});
8383
});
84+
85+
it('should not render Login form if logged in', () => {
86+
cy.registerUser().then((username) => {
87+
cy.visit('/login');
88+
cy.url().should('include', '/login');
89+
90+
// Check that the login form is not displayed
91+
cy.get('input[name="username"]').should('not.exist');
92+
cy.get('input[name="password"]').should('not.exist');
93+
94+
// Check that the logged-in message is displayed
95+
cy.contains('Already logged in').should('be.visible');
96+
97+
// Check that the username is displayed
98+
cy.contains(`You are currently logged in as ${username}`).should('be.visible');
99+
100+
// Check that the logout button is present
101+
cy.contains('button', 'Log out').should('be.visible');
102+
103+
// Test logout functionality - ignore uncaught exceptions for this part
104+
cy.on('uncaught:exception', () => {
105+
// returning false here prevents Cypress from failing the test
106+
return false;
107+
});
108+
cy.contains('button', 'Log out').click();
109+
110+
// After logout, the login form should be present again
111+
cy.get('input[name="username"]').should('exist');
112+
cy.get('input[name="password"]').should('exist');
113+
});
114+
});
84115
});

cypress/support/commands.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,25 @@ Cypress.Commands.add('registerUser', (username = null, password = 'securePasswor
3636
// Return the created username so it can be used in tests
3737
return cy.wrap(finalUsername);
3838
});
39+
40+
// Updated command in cypress/support/commands.js
41+
Cypress.Commands.add('registerForCourse', (courseName) => {
42+
cy.visit('/quilts');
43+
44+
if (courseName) {
45+
// Find and join a specific course by name
46+
cy.contains(courseName)
47+
.closest('[data-cy="available-course-card"]')
48+
.find('[data-cy="register-course-button"]')
49+
.click();
50+
} else {
51+
// Join the first available course
52+
cy.get('[data-cy="available-course-card"]')
53+
.first()
54+
.find('[data-cy="register-course-button"]')
55+
.click();
56+
}
57+
58+
// Wait for registration to complete
59+
cy.wait(1000);
60+
});

packages/common-ui/src/components/HeatMap.vue

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ export default defineComponent({
3939
type: Array as PropType<ActivityRecord[]>,
4040
default: () => [],
4141
},
42+
// Accept a function that can retrieve activity records
43+
activityRecordsGetter: {
44+
type: Function as PropType<() => Promise<ActivityRecord[]>>,
45+
default: null,
46+
},
4247
// Customize colors
4348
inactiveColor: {
4449
type: Object as PropType<Color>,
@@ -66,6 +71,8 @@ export default defineComponent({
6671
6772
data() {
6873
return {
74+
isLoading: false,
75+
localActivityRecords: [] as ActivityRecord[],
6976
heatmapData: {} as { [key: string]: number },
7077
weeks: [] as DayData[][],
7178
tooltipData: null as DayData | null,
@@ -81,6 +88,9 @@ export default defineComponent({
8188
height(): number {
8289
return 7 * (this.cellSize + this.cellMargin);
8390
},
91+
effectiveActivityRecords(): ActivityRecord[] {
92+
return this.localActivityRecords.length > 0 ? this.localActivityRecords : this.activityRecords;
93+
},
8494
},
8595
8696
watch: {
@@ -93,17 +103,32 @@ export default defineComponent({
93103
},
94104
},
95105
106+
async created() {
107+
if (this.activityRecordsGetter) {
108+
try {
109+
this.isLoading = true;
110+
this.localActivityRecords = await this.activityRecordsGetter();
111+
} catch (error) {
112+
console.error('Error fetching activity records:', error);
113+
} finally {
114+
this.isLoading = false;
115+
}
116+
}
117+
},
118+
96119
methods: {
97120
toDateString(d: string): string {
98121
const m = moment(d);
99122
return moment.months()[m.month()] + ' ' + m.date();
100123
},
101124
102125
processRecords() {
103-
console.log(`Processing ${this.activityRecords.length} records`);
126+
const records = this.effectiveActivityRecords;
127+
console.log(`Processing ${records.length} records`);
128+
104129
const data: { [key: string]: number } = {};
105130
106-
this.activityRecords.forEach((record) => {
131+
records.forEach((record) => {
107132
const date = moment(record.timeStamp).format('YYYY-MM-DD');
108133
data[date] = (data[date] || 0) + 1;
109134
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Misc. config for a study StudySessionConfig
3+
*/
4+
export type StudySessionConfig = {
5+
likesConfetti: boolean;
6+
};

0 commit comments

Comments
 (0)