This repository was archived by the owner on Feb 14, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 37
feat: Library Content Block Editor #411
Closed
Closed
Changes from all commits
Commits
Show all changes
49 commits
Select commit
Hold shift + click to select a range
27b2c6f
feat: initial editor layout + ui
1a3bc9b
feat: wip
rayzhou-bit 668c184
feat: wip2
rayzhou-bit d8a45fd
feat: wip3
rayzhou-bit 3f1887c
feat: polish
rayzhou-bit d66c7e9
test: add reducers and selectors tests
3734e01
fix: proptypes
rayzhou-bit 2b263ee
test: add blockselector test
ffd2e63
test: add three more JS tests
65eee08
fix: add tests for all but hooks.js
2674607
fix: initialize
rayzhou-bit e74c90f
fix: library api
rayzhou-bit 3d8d5b0
feat: api tweaks mostly with index.js
rayzhou-bit 5d2f783
feat: libraryselector.js api update
rayzhou-bit a9989da
feat: blockselector.js rewrite wip
rayzhou-bit 56c4c39
feat: general changes to api usage
rayzhou-bit 2b92174
feat: table saves candidates
rayzhou-bit 21bd883
feat: load candidate works
rayzhou-bit ab3e541
feat: v1 library selection
rayzhou-bit fe65556
fix: loading issue and polish
rayzhou-bit 45b9f40
fix: saving blocks
rayzhou-bit 1339322
feat: sort library dropdown alphabetically
rayzhou-bit c92c35f
feat: lint and some fixes
rayzhou-bit 48ce342
feat: lint and tests
rayzhou-bit 8fb9644
feat: candidate tuples and more tests
rayzhou-bit 9a56336
feat: more tests
rayzhou-bit 4bdf570
feat: why are tests so hard
rayzhou-bit 535bc14
feat: more tests
rayzhou-bit 2699795
feat: lint
rayzhou-bit be6cbe8
feat: merge main
rayzhou-bit bcc0984
feat: lint
rayzhou-bit be615b6
feat: some more api tests
rayzhou-bit 87fbfa7
feat: selectors test
rayzhou-bit 6265f5a
feat: remove fetchV2LibraryMetadata
rayzhou-bit 3a689a4
feat: v1 library api update
rayzhou-bit 0d51711
feat: fix
rayzhou-bit b09ed11
feat: lint
rayzhou-bit 32c4fb3
feat: library version should be string
rayzhou-bit 208070a
feat: failure tests
rayzhou-bit 02f6ac3
feat: merge conflict
rayzhou-bit 3ea40d8
feat: LCB children
rayzhou-bit fd6d530
feat: new fetch children api
rayzhou-bit 1c783d7
feat: test and lint
rayzhou-bit 0bfa336
feat: use usage id and fetch v1 library block
rayzhou-bit 8553ac6
feat: regex fix and v1 library version
rayzhou-bit 9db63b4
feat: lint
rayzhou-bit 0c68a92
feat: v1 api fix and candidate saving
rayzhou-bit 01f6296
feat: test and lint
rayzhou-bit d94280c
Merge remote-tracking branch 'upstream/main' into feat--library-conte…
kdmccormick File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
149 changes: 149 additions & 0 deletions
149
src/editors/containers/LibraryContentEditor/BlocksSelector.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| import React from 'react'; | ||
| import { connect } from 'react-redux'; | ||
| import PropTypes from 'prop-types'; | ||
| import { FormattedMessage, injectIntl } from '@edx/frontend-platform/i18n'; | ||
| import { CheckboxControl, DataTable, Form } from '@edx/paragon'; | ||
|
|
||
| import { modes } from './constants'; | ||
| import { selectors } from '../../data/redux'; | ||
| import { useBlocksSelectorHook } from './hooks'; | ||
| import messages from './messages'; | ||
|
|
||
| export const SELECT_ONE_TEST_ID = 'selectOne'; | ||
|
|
||
| export const RowCheckbox = ({ row }) => { | ||
| const { | ||
| indeterminate, | ||
| checked, | ||
| ...toggleRowSelectedProps | ||
| } = row.getToggleRowSelectedProps(); | ||
|
|
||
| return ( | ||
| <div className="text-center"> | ||
| <CheckboxControl | ||
| {...toggleRowSelectedProps} | ||
| title="Toggle row selected" | ||
| checked={checked} | ||
| isIndeterminate={false} | ||
| data-testid={SELECT_ONE_TEST_ID} | ||
| /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export const BlocksSelector = ({ | ||
| // redux | ||
| blocks, | ||
| mode, | ||
| candidates, | ||
| libraries, | ||
| savedLibraryId, | ||
| selectedLibraryId, | ||
| v1BlockRequests, | ||
| }) => { | ||
| const { | ||
| tableDataLoaded, | ||
| data, | ||
| initialRows, | ||
| onSelectedRowsChanged, | ||
| } = useBlocksSelectorHook({ | ||
| blocks, | ||
| candidates, | ||
| libraries, | ||
| savedLibraryId, | ||
| selectedLibraryId, | ||
| v1BlockRequests, | ||
| }); | ||
|
|
||
| const columns = [ | ||
| { | ||
| Header: 'Name', | ||
| accessor: 'display_name', | ||
| }, | ||
| { | ||
| Header: 'Block Type', | ||
| accessor: 'block_type', | ||
| }, | ||
| ]; | ||
|
|
||
| const selectColumn = { | ||
| id: 'selection', | ||
| Header: () => null, | ||
| Cell: RowCheckbox, | ||
| disableSortBy: true, | ||
| }; | ||
|
|
||
| if (selectedLibraryId === null || mode !== modes.selected.value || !tableDataLoaded) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <div className="mb-5 pt-3 border-top"> | ||
| <Form.Label> | ||
| <FormattedMessage {...messages.tableInstructionLabel} /> | ||
| </Form.Label> | ||
| <DataTable | ||
| key={selectedLibraryId} | ||
| columns={columns} | ||
| data={data} | ||
| itemCount={data.length} | ||
| isSelectable | ||
| isPaginated | ||
| isSortable | ||
| initialState={{ selectedRowIds: initialRows }} | ||
| manualSelectColumn={selectColumn} | ||
| onSelectedRowsChanged={onSelectedRowsChanged} | ||
| > | ||
| <DataTable.TableControlBar /> | ||
| <DataTable.Table /> | ||
| <DataTable.EmptyTable content="No blocks found." /> | ||
| <DataTable.TableFooter /> | ||
| </DataTable> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| RowCheckbox.defaultProps = { | ||
| row: {}, | ||
| }; | ||
|
|
||
| RowCheckbox.propTypes = { | ||
| row: PropTypes.shape({ | ||
| getToggleRowSelectedProps: PropTypes.func.isRequired, | ||
|
connorhaugh marked this conversation as resolved.
|
||
| }), | ||
| }; | ||
|
|
||
| BlocksSelector.defaultProps = { | ||
| blocks: [], | ||
| candidates: [], | ||
| libraries: [], | ||
| mode: '', | ||
| savedLibraryId: null, | ||
| selectedLibraryId: null, | ||
| v1BlockRequests: [], | ||
| }; | ||
|
|
||
| BlocksSelector.propTypes = { | ||
| // redux | ||
| blocks: PropTypes.arrayOf(PropTypes.shape({})), | ||
| candidates: PropTypes.shape([]), | ||
| libraries: PropTypes.shape([]), | ||
| mode: PropTypes.string, | ||
| savedLibraryId: PropTypes.string, | ||
| selectedLibraryId: PropTypes.string, | ||
| v1BlockRequests: PropTypes.shape({}), | ||
| }; | ||
|
|
||
| export const mapStateToProps = (state) => ({ | ||
| blocks: selectors.library.blocks(state), | ||
| candidates: selectors.library.candidates(state), | ||
| libraries: selectors.library.libraries(state), | ||
| mode: selectors.library.mode(state), | ||
| savedLibraryId: selectors.library.savedLibraryId(state), | ||
| selectedLibraryId: selectors.library.selectedLibraryId(state), | ||
| v1BlockRequests: selectors.library.v1BlockRequests(state), | ||
| }); | ||
|
|
||
| export const mapDispatchToProps = {}; | ||
|
|
||
| export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(BlocksSelector)); | ||
111 changes: 111 additions & 0 deletions
111
src/editors/containers/LibraryContentEditor/BlocksSelector.test.jsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| import React from 'react'; | ||
| import { screen, render } from '@testing-library/react'; | ||
| import '@testing-library/jest-dom/extend-expect'; | ||
| import userEvent from '@testing-library/user-event'; | ||
| import { IntlProvider } from '@edx/frontend-platform/i18n'; | ||
| import { BlocksSelector, RowCheckbox, SELECT_ONE_TEST_ID } from './BlocksSelector'; | ||
|
|
||
| jest.unmock('@edx/paragon'); | ||
| jest.unmock('@edx/paragon/icons'); | ||
|
|
||
| jest.mock('./hooks', () => ({ | ||
| useBlocksSelectorHook: jest.fn().mockReturnValue({ | ||
| tableDataLoaded: true, | ||
| data: [ | ||
| { id: 1, display_name: 'Block 1', block_type: 'Type A' }, | ||
| { id: 2, display_name: 'Block 2', block_type: 'Type B' }, | ||
| ], | ||
| initialRows: {}, | ||
| onSelectedRowsChanged: jest.fn(), | ||
| }), | ||
| })); | ||
|
|
||
| function renderComponent(props) { | ||
| return render( | ||
| <IntlProvider locale="en"> | ||
| <BlocksSelector {...props} /> | ||
| </IntlProvider>, | ||
| ); | ||
| } | ||
|
|
||
| const mockProps = { | ||
| initialRows: {}, | ||
| mode: 'selected', | ||
| setCandidatesForLibrary: jest.fn(), | ||
| selectedLibraryId: 'exampleLibraryId', | ||
| }; | ||
| const mockOnChange = jest.fn(); | ||
| const defaultToggleRowSelectedProps = { | ||
| indeterminate: false, | ||
| checked: false, | ||
| onChange: mockOnChange, | ||
| }; | ||
| const mockToggleRowSelectedProps = jest.fn(() => defaultToggleRowSelectedProps); | ||
| const defaultRow = { | ||
| id: 'foo', | ||
| getToggleRowSelectedProps: mockToggleRowSelectedProps, | ||
| }; | ||
| const checkedRow = { | ||
| ...defaultRow, | ||
| getToggleRowSelectedProps: jest.fn(() => ({ | ||
| ...defaultToggleRowSelectedProps, | ||
| checked: true, | ||
| })), | ||
| }; | ||
|
|
||
| describe('BlocksSelector', () => { | ||
| it('renders when selectedLibraryId is not null', () => { | ||
| const { queryByText } = renderComponent(mockProps); | ||
|
|
||
| // make sure that the relevant columns are there | ||
| expect(queryByText('Name')).toBeTruthy(); | ||
| expect(queryByText('Block Type')).toBeTruthy(); | ||
| // make sure that the relevant rows are there | ||
| }); | ||
|
|
||
| it('does not render when selectedLibraryId is null', () => { | ||
| const { container } = renderComponent({ ...mockProps, selectedLibraryId: null }); | ||
| expect(container.firstChild).toBeFalsy(); | ||
| }); | ||
|
|
||
| it('renders when mode is selected', () => { | ||
| const { queryByText } = renderComponent(mockProps); | ||
| expect(queryByText('Name')).toBeTruthy(); | ||
| }); | ||
|
|
||
| it('does not render when mode is not selected', () => { | ||
| const { container } = renderComponent({ ...mockProps, mode: 'soMeThingElse' }); | ||
| expect(container.firstChild).toBeFalsy(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('RowCheckbox', () => { | ||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| }); | ||
|
|
||
| it('renders a checkbox', () => { | ||
| render(<RowCheckbox contextKey="emails" row={defaultRow} />); | ||
| const checkbox = screen.getByTestId(SELECT_ONE_TEST_ID); | ||
| expect(checkbox).toBeInTheDocument(); | ||
| expect(checkbox).toHaveProperty('checked', false); | ||
| }); | ||
|
|
||
| it('renders a selected checkbox', () => { | ||
| render( | ||
| <RowCheckbox contextKey="emails" row={checkedRow} />, | ||
| ); | ||
| const checkbox = screen.getByTestId(SELECT_ONE_TEST_ID); | ||
| expect(checkbox).toBeInTheDocument(); | ||
| expect(checkbox).toHaveProperty('checked', true); | ||
| }); | ||
|
|
||
| it('deselects the row when selected checkbox is checked', () => { | ||
| render( | ||
| <RowCheckbox contextKey="emails" row={defaultRow} />, | ||
| ); | ||
| const checkbox = screen.getByTestId(SELECT_ONE_TEST_ID); | ||
| userEvent.click(checkbox); | ||
| expect(mockOnChange).toHaveBeenCalledTimes(1); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.