+ Ordinal features have a discrete number of categories, and the categories have a logical order (rank). Some examples include size ("small", "medium", "large"), or rank results ("first", "second", "third").
+
+
+ You can specify these features and their rank in two ways:
+
+ 1) In the text input box opened by the button to the left, using the format described in the box
+
+ 2) or, in the Dataset Preview table below: use the dropdown boxes to specify ordinal features, then rank them using the drag-and-drop list of unique categories.
+
+ This site is using 'Categorical' to mean a Nominal feature, per custom in the ML community. Categorical features have a discrete number of categories that do not have an intrinsic order. Some examples include sex ("male", "female") or eye color ("brown", "green", "blue"...).
+
+
+ You can specify these features in two ways:
+
+ 1) In the text input box opened by the button to the left, using the format described in the box
+
+ 2) or, in the Dataset Preview table below: use the dropdown boxes to specify categorical features.
+
+
+`;
diff --git a/lab/webapp/src/components/FileUpload/fileUpload.test.js b/lab/webapp/src/components/FileUpload/fileUpload.test.js
index fb7d51d05..b8dda1061 100644
--- a/lab/webapp/src/components/FileUpload/fileUpload.test.js
+++ b/lab/webapp/src/components/FileUpload/fileUpload.test.js
@@ -29,12 +29,8 @@ import FileUpload from './';
//import SceneHeader from '../SceneHeader';
// try getting react pieces and framework for test rendering
import React from 'react';
-import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
-import fetch from 'jest-fetch-mock';
-import fetchMock from 'fetch-mock';
-import Dropzone from 'react-dropzone'
const middlewares = [thunk];
const initialState = {};
@@ -45,470 +41,780 @@ import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
-// mock out stuff fileupload depends on
-//jest.mock('../SceneHeader', () => 'Scene_Header');
-
+//// NOTE on getting state
+//
+// The enzyme wrapper method 'state' (and related methods) is not working on mount().
+// I think it's because the component is wrapped with redux connect().
+// So you can use shallow().dive().state(),
+// or more direclty shallow().dive().instance().state
+// I don't know which is better.
+//
describe('basic testing of fileupload react component', () => {
let store = mockStore(initialState);
- let testFileUpload;
- let tree;
- let fakeFile = {target: {files: [{name: 'iris.csv'}]}};
- let fakeFileTsv = {target: {files: [{name: 'iris.tsv'}]}};
- let badFakeFile = {target: {files: [{name: 'iris.txt'}]}};
+ let fullDom;
+ let shallowDom;
+ let instance;
+ let badFakeFile = {name: 'iris.txt'};
// basic bookkeeping before/after each test; mount/unmount component, should be
// similar to how piece will actually work in browser
beforeEach(() => {
- testFileUpload = mount();
- //tree = renderer.create().toJSON();
+ fullDom = mount();
+ //NOTE with react-redux, need 'dive()' to get the 'true' (not sure of the term) component instance.
+ shallowDom = shallow().dive();
+ instance = shallowDom.instance();
})
afterEach(() => {
- testFileUpload.unmount();
+ fullDom.unmount();
})
- // test for existence
- it('create mock fileupload component, test for existence', () => {
- //const testFileUpload = shallow();
- testFileUpload.setProps({ name: 'bar' });
- //let tree = component.toJSON();
- expect(testFileUpload.name()).toEqual('Connect(FileUpload)');
- expect(testFileUpload.props().testProp).toEqual('hello');
- expect(testFileUpload.props().name).toEqual('bar');
- // test snapshot
- //expect(tree).toMatchSnapshot();
+ //test that the componement instance is viable
+ //NOTE 'it' is an alias for 'test'
+ it('DONE - test instance', () => {
+ expect(instance).not.toEqual(null);
+ //Test calling a method on the instance
+ expect(instance.instanceTest()).toEqual('foobar');
+ //Test retrieving state from the instance. See note above about accessing state.
+ expect(instance.state.testStateValue).toEqual('foobar');
+ expect(shallowDom.state('testStateValue')).toEqual('foobar');
})
- it('simple change to component state', () => {
- expect(testFileUpload.props().name).toBeUndefined();
- expect(testFileUpload.state('dependentCol')).toBeUndefined();
- testFileUpload.setState({dependentCol: 'class'})
- expect(testFileUpload.state('dependentCol')).toEqual('class');
+ // test for existence
+ it('DONE - fileupload component, test for existence', () => {
+ //console.log('jest console log!'); //this works, outputs to console at test script runtime
+ // Find the component itself
+ expect(fullDom.find(FileUpload)).toHaveLength(1);
+ fullDom.setProps({ name: 'bar' });
+ expect(fullDom.name()).toEqual('Connect(FileUpload)');
+ expect(fullDom.props().testProp).toEqual('hello');
+ expect(fullDom.props().name).toEqual('bar');
})
- it('TODO - simulate user entering data with file upload form inputs', () => {
- // asked about how to simulate user actions here, using enzyme simulate doesn't quite
- // work, using 'onChange' prop to fake user action:
- // https://stackoverflow.com/questions/55638365/how-to-access-internal-pieces-of-react-component-and-mock-api-call-with-jest-e/55641884#55641884
-
- // these actions are supposed to tigger event handlers in the component being
- // tested (which they do) & update component react state (which doesn't appear to happen)
- // this might be a limitation of enzyme
-
-// expect(testFileUpload.find(FileUpload)).to.have.lengthOf(1);
-/*
-
- // this should create a browser console error - using javascript library to
- // create a file preview which attempts to parse given input, if input not a
- // file/blob the error is generated. The file onChange handler attempts to
- // create the file preview and set the selected file obj and file name in
- // the component's react state
-
- testFileUpload.find('input').at(0).prop('onChange')(fakeFile);
-
- // update() is supposed to forceUpdate/re-render the component
- testFileUpload.update();
- // manually updating, component react state does not contain anything, in any
- // case need to call update() to access elements by html dom ID
-
-
- // the following is a standin for user input entering metadata
-
- let depColTextField = testFileUpload.find('#dependent_column_text_field_input').at(0);
- // still need to access with 'at', using find('#dependent_column_text_field_input')
- // returns 4 nodes somehow
- depColTextField.prop('onChange')(
- {target:{value: 'test_class'}},
- {value:'test dep input'}
- );
- let ordTextArea = testFileUpload.find('#ordinal_features_text_area_input');
- ordTextArea.prop('onChange')(
- {target:{value: {testOrdKey: 'testHello'}}},
- {value:'test ord input'}
- );
- let catTextArea = testFileUpload.find('#categorical_features_text_area_input');
- catTextArea.prop('onChange')(
- {target:{value: 'testCatHello1, testCatHello2'}},
- {value:'test cat input'}
- );
- // cheating for now and just updating component state directly...
- testFileUpload.setState({
- selectedFile: fakeFile.target.files[0],
- ordinalFeatures: {testOrdKey: 'testHello'},
- catFeatures: 'testCatHello1, testCatHello2',
- dependentCol: 'test_class'
- });
- //console.log('test state: ', testFileUpload.state());
- //console.log('test file: ', testFileUpload.state('selectedFile'));
- // ...and checking for state which was just manually set above
- expect(testFileUpload.state('selectedFile')).toEqual(fakeFile.target.files[0]);
- expect(testFileUpload.state('ordinalFeatures')).toEqual({testOrdKey: 'testHello'});
- expect(testFileUpload.state('catFeatures')).toEqual('testCatHello1, testCatHello2');
- expect(testFileUpload.state('dependentCol')).toEqual('test_class');
- */
+ //snapshot of component
+ //I believe shallow is better since it won't include child components
+ it('DONE - full component snapshot', () => {
+ expect(shallow().dive()).toMatchSnapshot();
})
- it('TODO - try uploading non csv/tsv file type', () => {
-/*
- testFileUpload.find('input').at(0).prop('onChange')(badFakeFile);
- testFileUpload.update();
- //expect(testFileUpload.state('selectedFile')).toEqual(badFakeFile.target.files[0]);
- expect(testFileUpload.state('selectedFile')).toBeUndefined();
- let formBody = testFileUpload.find('#file-upload-form-input-area');
+ // the intended behavior of this component is to hide the fields to enter info
+ // about the file being uploaded until a file with a testFilename has been selected
+ it('DONE - check UI form is hidden w/o a file selection', () => {
+ let formBody = shallowDom.find('#file-upload-form-input-area');
+ expect(formBody.length).toEqual(1)
// check for CSS style which hides form
expect(formBody.hasClass('file-upload-form-hide-inputs')).toEqual(true);
expect(formBody.hasClass('file-upload-form-show-inputs')).toEqual(false);
-*/
})
+
+ it('TODO - try selecting non-csv/tsv file type', () => {
+
+ let dropzoneButton = fullDom.find("#file-dropzone");
+ expect(dropzoneButton).toHaveLength(1);
+ // Simulate callback receiving bad file obj
+ dropzoneButton.at(0).prop('onDropAccepted')([badFakeFile]);
+ // Should not have changed file object
+ expect(shallowDom.state('selectedFile')).toBeNull();
+ // Check the UI isn't showing for file specifications
+ let formBody = fullDom.find('#file-upload-form-input-area');
+ // check for CSS style which hides form
+ expect(formBody.hasClass('file-upload-form-hide-inputs')).toEqual(true);
+ expect(formBody.hasClass('file-upload-form-show-inputs')).toEqual(false);
+ })
+
+}) //describe
- it('TODO - try testing generateFileData - good input', () => {
-/*
- // use dive() to get at inner FileUpload class functions -
- // https://github.com/airbnb/enzyme/issues/208#issuecomment-292631244
- const shallowFileUpload = shallow().dive();
- // all input from user will be read as strings
- const fakeUserInput = {
- depCol: 'target_class',
- catCols: 'a, b, c',
- ordFeats: '{"species": ["cat", "dog", "bird"]}'
- };
- // add fake file with filename so generateFileData will do something, if
- // no file & filename present generateFileData returns blank/empty formData
- shallowFileUpload.setState({
- selectedFile: fakeFile.target.files[0],
- dependentCol: fakeUserInput.depCol,
- catFeatures: fakeUserInput.catCols,
- ordinalFeatures: fakeUserInput.ordFeats
+// Test various functionality with a file blob that
+// simulates actual data loaded
+describe('series of tests with mock file blob', () => {
+ let store = mockStore(initialState);
+ let fullDom;
+ let shallowDom;
+ let instance;
+
+ // Create a test blob.
+ // Simulates raw data loaded by file I/O, before it's parsed as csv data.
+ //
+ let featureNames=['col1','col2','class','col3'];
+ let depColumnIndex = 2;
+ // The data, with each feature as a row
+ let dataByFeature = [
+ ['one','three','two','one'], //column 0
+ ['cat','dog','cat','bird'], //column 1, etc..
+ [1,2,3,2.5],
+ ['yes','no','yes','42']
+ ]
+ // Make CSV version of data
+ let dataCSV = featureNames.join()+`\n`;
+ for(let row=0; row < dataByFeature[0].length; row++) {
+ let line = "";
+ for(let col=0; col < featureNames.length; col++) {
+ line += dataByFeature[col][row];
+ if(col < (featureNames.length-1))
+ line += ','
+ }
+ line += '\n';
+ dataCSV += line;
+ }
+
+ let catFeaturesOrig = [featureNames[0],featureNames[1],featureNames[3]];
+ let testFilename = 'testFile.csv';
+ let blob = new Blob([dataCSV], {type: 'text/csv'});
+ blob.lastModifiedDate = new Date();
+ let testFileBlob = new File([blob], testFilename);
+
+ function getFeatureTypesDefault(){
+ return [instance.featureTypeCategorical, instance.featureTypeCategorical, instance.featureTypeNumeric, instance.featureTypeCategorical];
+ }
+
+ // Helper method
+ function setAllFeaturesToDefaults() {
+ instance.clearDependentFeature();
+ instance.setAllFeatureTypes('autoDefault');
+ // This will clear the previous value of ordinal features that's otherwise stored.
+ instance.ordinalFeaturesClearToDefault(true);
+ // Set predicition type
+ instance.setState( {
+ predictionType: instance.defaultPredictionType,
});
-
- // expect list of comma separated cat cols to get parsed with string split
- // dependent columns will be stored as is (type string)
- // ordFeats will parse given JSON
- const expectedInput = {
- depCol: 'target_class',
- catCols: ['a', 'b', 'c'],
- ordFeats: {
- species: ["cat", "dog", "bird"]
+ shallowDom.update();
+ }
+
+ // Helper method so this is kept in one place
+ // typeOrArray - type string to expect all to be, or 'default' for auto defaults, or array of types
+ function testFeatureTypes(typeOrArray)
+ {
+ let testArray;
+ if(Array.isArray(typeOrArray)){
+ testArray = typeOrArray;
+ }
+ else {
+ if(typeOrArray === 'default'){
+ testArray = getFeatureTypesDefault();
}
- };
- // use instance to get access to inner function
- const instance = shallowFileUpload.instance();
- // create spy, check function gets called
- const spy = jest.spyOn(instance, 'generateFileData');
- const testData = instance.generateFileData(); // FormData
- // get stuff stored in returned formdata, stingified in preparation to make
- // network request
- const metadata = JSON.parse(testData.get('_metadata'));
- expect(spy).toBeCalled();
- // value of _metadata defined in generateFileData
- expect(metadata.dependent_col).toEqual(expectedInput.depCol);
- expect(metadata.categorical_features).toEqual(expectedInput.catCols);
- expect(metadata.ordinal_features).toEqual(expectedInput.ordFeats);
-*/
+ else{
+ testArray = [0,0,0,0];
+ testArray.fill(typeOrArray);
+ }
+ }
+ //Don't test in a loop, it makes error reporting harder to understand
+ expect(featureNames.length).toBe(4);
+ expect(testArray.length).toBe(4);
+ expect(instance.getFeatureType(featureNames[0])).toEqual(testArray[0]);
+ expect(instance.getFeatureType(featureNames[1])).toEqual(testArray[1]);
+ expect(instance.getFeatureType(featureNames[2])).toEqual(testArray[2]);
+ expect(instance.getFeatureType(featureNames[3])).toEqual(testArray[3]);
+ }
+
+ // Return obj { result: true if error modal is showing, header: header string, content: content string }
+ function errorModalIsShowing() {
+ let res = {
+ result: shallowDom.find("#error_modal_dialog").length > 0 && shallowDom.find("#error_modal_dialog").at(0).prop('open'),
+ header: instance.state.errorModalHeader,
+ content: instance.state.errorModalContent
+ }
+ return res;
+ }
+
+ function closeErrorModal() {
+ // I first tried these methods to simulate user UI actions
+ // to close the error modal, but they're not working.
+ //shallowDom.find("#error_modal_dialog").at(0).simulate('keydown', {keyCoode: 27});
+ //shallowDom.find("#file-upload-form-input-area").at(0).simulate('click');
+ //shallowDom.find(".close").at(0).simulate('click'); //tries to find the close icon on the modal, but it fails
+ instance.handleErrorModalClose();
+ shallowDom.update();
+ }
+
+ // Use beforeAll so that the blob stays loaded w/out having
+ // to do all the tests in the mocked callback below.
+ beforeAll(() => {
+ fullDom = mount();
+ shallowDom = shallow().dive();
+ instance = shallowDom.instance();
})
-
- it('TODO - Select tsv file - expect form to be displayed', () => {
-/*
- testFileUpload.find('input').at(0).prop('onChange')(fakeFileTsv);
-
- // update() is supposed to forceUpdate/re-render the component
- testFileUpload.update();
- testFileUpload.setState({
- selectedFile: fakeFileTsv.target.files[0]
- });
- let formBody = testFileUpload.find('#file-upload-form-input-area');
-
- // check for CSS style which hides form
- expect(formBody.hasClass('file-upload-form-hide-inputs')).toEqual(false);
- expect(formBody.hasClass('file-upload-form-show-inputs')).toEqual(true);
- expect(testFileUpload.state('selectedFile')).toEqual(fakeFileTsv.target.files[0]);
-*/
+ afterAll(() => {
+ fullDom.unmount();
})
- it('TODO - try testing generateFileData - bad input, no ordinal features', () => {
-/*
- // use dive() to get at inner FileUpload class functions -
- // https://github.com/airbnb/enzyme/issues/208#issuecomment-292631244
- const shallowFileUpload = shallow().dive();
- // all input from user will be read as strings
- const fakeUserInput = {
- depCol: 'target_c@#$@#$',
- catCols: 'a, b, c , 4,,, ,',
- ordFeats: ''
- };
- // add fake file with filename so generateFileData will do something, if
- // no file & filename present generateFileData returns blank/empty formData
- shallowFileUpload.setState({
- selectedFile: fakeFileTsv.target.files[0],
- dependentCol: fakeUserInput.depCol,
- catFeatures: fakeUserInput.catCols,
- ordinalFeatures: fakeUserInput.ordFeats
+ // DO THIS TEST FIRST for this group of tets.
+ // Load the simulated file blob. Handles async behavior.
+ it('DONE - load simulated file blob', done => {
+ // Mock the method FileUpload.handleSelectedFileCompletedStub for unit testing.
+ // It gets called when file blob
+ // is loaded successfully, just for unit testing since the file loader
+ // calls a callback when complete, and thus otherwise wouldn't be
+ // handled before this test copmletes. The 'done' method here is
+ // provided by jest to handle async testing.
+ // Messy but it works.
+ const mockcb = jest.fn( () => {
+ // Test that it finished loading successfully
+ expect(mockcb.mock.calls.length).toBe(1);
+
+ // Test that the file object is set
+ expect(instance.state.selectedFile.name).toEqual(testFilename);
+
+ // Tell jest we're done with this test.
+ done();
});
-
- // expect list of comma separated cat cols to get parsed with string split
- // (remove whitespace and empty strings)
- // dependent columns will be stored as is (type string)
- // ordFeats will parse given JSON, if not proper JSON return empty obj
- const expectedInput = {
- depCol: 'target_c@#$@#$',
- catCols: ['a', 'b', 'c', '4'],
- ordFeats: ''
- };
- // use instance to get access to inner function
- const instance = shallowFileUpload.instance();
- // create spy, check function gets called
- const spy = jest.spyOn(instance, 'generateFileData');
- const testData = instance.generateFileData(); // FormData
- // get stuff stored in returned formdata, stingified in preparation to make
- // network request
- const metadata = JSON.parse(testData.get('_metadata'));
- expect(spy).toBeCalled();
- // value of _metadata defined in generateFileData
- expect(metadata.dependent_col).toEqual(expectedInput.depCol);
- expect(metadata.categorical_features).toEqual(expectedInput.catCols);
- expect(metadata.ordinal_features).toEqual(expectedInput.ordFeats);
-*/
+ instance.handleSelectedFileCompletedStub = mockcb;
+
+ // Load file obj directly via handler
+ instance.handleSelectedFile([testFileBlob]);
+ }, 15000/* long timeout just in case*/)
+
+ // ** NOTE **
+ // Subsquent tests rely on the wrapper from above still being loaded
+ // and having the test file blob loaded.
+
+ it('test features states after file blob load', () => {
+ // Verify error modal is not showing
+ expect(errorModalIsShowing().result).toBe(false);
+
+ // Test the auto-assigned feature types and getFeatureType()
+ // Expect dep. column to be set
+ let expected = getFeatureTypesDefault();
+ expected[depColumnIndex] = instance.featureTypeDependent;
+ testFeatureTypes(expected);
+
+ // No ordinal features should be specified. Object should be empty
+ expect(instance.state.ordinalFeaturesObject).toEqual({});
+
+ // Dependent column should be set to column with name 'class' after load,
+ // because the name 'class' is used to auto-detect dependent column
+ expect(instance.getDependentColumn()).toEqual(featureNames[depColumnIndex]);
+
+ // Set all features to default type and check. This will
+ // clear dependent column.
+ setAllFeaturesToDefaults();
+ testFeatureTypes('default');
})
- it('TODO - try testing generateFileData - bad input, with ordinal features', () => {
-/*
- // use dive() to get at inner FileUpload class functions -
- // https://github.com/airbnb/enzyme/issues/208#issuecomment-292631244
- const shallowFileUpload = shallow().dive();
- // all input from user will be read as strings
- const fakeUserInput = {
- depCol: 'target_c@#$@#$',
- catCols: 'a, b, c , 4,,, ,',
- ordFeats: '{"species": ["cat", "dog", "bird"]}{"missing_comma": ["one","two"]}'
- };
- // add fake file with filename so generateFileData will do something, if
- // no file & filename present generateFileData returns blank/empty formData
- shallowFileUpload.setState({
- selectedFile: fakeFile.target.files[0],
- dependentCol: fakeUserInput.depCol,
- catFeatures: fakeUserInput.catCols,
- ordinalFeatures: fakeUserInput.ordFeats
- });
-
- // expect list of comma separated cat cols to get parsed with string split
- // (remove whitespace and empty strings)
- // dependent columns will be stored as is (type string)
- // ordFeats will parse given JSON, if not proper JSON return empty obj
- const expectedInput = {
- depCol: 'target_c@#$@#$',
- catCols: ['a', 'b', 'c', '4'],
- ordFeats: ''
- };
- // use instance to get access to inner function
- const instance = shallowFileUpload.instance();
- // create spy, check function gets called
- const spy = jest.spyOn(instance, 'generateFileData');
- const testData = instance.generateFileData(); // FormData
- // returned content from generateFileData should be object containing error
- //console.log('error test: ', testData);
- expect(testData.errorResp).toBeDefined();
- expect(testData.errorResp).toEqual('SyntaxError: Unexpected token { in JSON at position 35');
-*/
+ it('DONE - test feature assignment behaviors via direct manipulation', () => {
+ // Test assigning a categorical feature to type numeric.
+ // Should silently reject and not change to numeric.
+ instance.setFeatureType(featureNames[0], instance.featureTypeNumeric);
+ expect(instance.getFeatureType(featureNames[0])).toEqual(instance.featureTypeCategorical);
+
+ // Test getting array of categorical features
+ let catFeatures = [...catFeaturesOrig];
+ expect(instance.getCatFeatures()).toEqual(catFeatures);
+
+ // Set ordinal feature, w/out specifying rank
+ let ordFeature = featureNames[0];
+ instance.setFeatureType(ordFeature, instance.featureTypeOrdinal);
+ expect(instance.getFeatureType(ordFeature)).toEqual(instance.featureTypeOrdinal);
+ let ordsExpected = {[ordFeature]: ["one","three","two"]};
+ expect(instance.state.ordinalFeaturesObject).toEqual(ordsExpected);
+ // Should not be included as a categorical feature
+ catFeatures = catFeatures.filter( val => val !== ordFeature); //remove ordinal feature
+ expect(instance.getCatFeatures().find(el => el === ordFeature)).toEqual(undefined);
+
+ // Reset to default types, unset dependent, and verify
+ setAllFeaturesToDefaults();
+ testFeatureTypes('default');
+
+ // Set all to ordinal
+ // Note that features that default to numeric can still be set as categorical or ordinal
+ instance.setAllFeatureTypes(instance.featureTypeOrdinal);
+ testFeatureTypes(instance.featureTypeOrdinal);
+
+ // Set all to categorical
+ instance.setAllFeatureTypes(instance.featureTypeCategorical);
+ testFeatureTypes(instance.featureTypeCategorical);
+
+ // Set all to numeric
+ // This should not set default-categorical types to numeric. They should be left as-is.
+ instance.setAllFeatureTypes(instance.featureTypeNumeric);
+ testFeatureTypes('default'); // Should now all equal default types because of how I've structured the test up to here
+
+ // Invalid feature names should be handled properly
+ expect(instance.validateFeatureName('The Spanish Inquisition')).toEqual(false);
+ // Should return default type for invalid feature name
+ expect(instance.getFeatureType('xkcd4eva')).toEqual(instance.featureTypeDefault);
+ expect(instance.getFeatureDefaultType('Frank the Furter')).toEqual(instance.featureTypeDefault);
})
-}) //describe
-
-//
-// describe('testing user input with table', () => {
-// describe.each`
-// testname | dependent_column | categorical_cols | ordinal_features | expected_cat | expected_ord
-// ${`Good input - no cat or ord`} | ${`test_class`} | ${""} | ${""} | ${[]} | ${{}}
-// ${`Good input - cat no ord`} | ${`test_class`} | ${"cat1, cat2"} | ${""} | ${["cat1","cat2"]} | ${{}}
-// ${`Good input - cat and ord`} | ${`test_class`} | ${"cat1, cat2"} | ${'{"species": ["cat", "dog", "bird"]}'}| ${["cat1","cat2"]} | ${{'species': ["cat", "dog", "bird"]}}
-// `("test good input", ({testname, dependent_column, categorical_cols, ordinal_features, expected_cat, expected_ord}) => {
-// it(`${testname}`, () => {
-// //console.log(`${testname} test`);
-//
-// let store = mockStore(initialState);
-// const shallowFileUpload = shallow().dive();
-// let testFileUpload;
-// let tree;
-// let fakeFile = {target: {files: [{name: 'iris.csv'}]}};
-// const expectedInput = {
-// depCol: 'test_class',
-// catCols: ['cat1', 'cat2'],
-// ordFeats: {
-// species: ["cat", "dog", "bird"]
-// }
-// };
-//
-// shallowFileUpload.setState({
-// selectedFile: fakeFile.target.files[0],
-// dependentCol: `${dependent_column}`,
-// catFeatures: `${categorical_cols}`,
-// ordinalFeatures: `${ordinal_features}`
-// });
-//
-//
-// // use instance to get access to inner function
-// const instance = shallowFileUpload.instance();
-// // create spy, check function gets called
-// const spy = jest.spyOn(instance, 'generateFileData');
-// const testData = instance.generateFileData(); // FormData
-// console.log(`test data: `, testData);
-// // get stuff stored in returned formdata, stingified in preparation to make
-// // network request
-// const metadata = JSON.parse(testData.get('_metadata'));
-// console.log(`test data: `, metadata);
-// expect(spy).toBeCalled();
-// // value of _metadata defined in generateFileData
-// expect(metadata.dependent_col).toEqual(`${dependent_column}`);
-// expect(metadata.categorical_features).toEqual(`${expected_cat}`);
-// expect(metadata.ordinal_features).toEqual(`${expected_ord}`);
-// })
-// })
-//
-// // describe.each`
-// // testname | dependent_column | categoricals | ordinal
-// // ${`Good input - no cat or ord`} | ${`test_class`} | ${[]} | ${{}}
-// // ${`Good input - no cat or ord`} | ${`test_class`} | ${"cat1, cat2"} | ${{}}
-// // `('test good input', ({testname, dependent_column, categorical, ordinal}) => {
-// // it(`${testname}`, () => {
-// // console.log(`${testname} test`);
-// // })
-// // });
-// })
-//
+ it('DONE - test dependent feature and prediction type behaviors via direct manipulation', () => {
+ //Reset feature types
+ setAllFeaturesToDefaults();
+
+ // Dependent feature should be unset
+ expect(instance.getDependentColumn()).toEqual(undefined);
+
+ // Assigning dependent feature
+ let dep1 = featureNames[2];
+ instance.setFeatureType(dep1, instance.featureTypeDependent);
+ expect(instance.getFeatureType(dep1)).toEqual(instance.featureTypeDependent);
+ expect(instance.getDependentColumn()).toEqual(dep1);
+
+ // Reassigning dependent feature should reset the previous one
+ // to its default type
+ let dep2 = featureNames[3];
+ instance.setFeatureType(dep2, instance.featureTypeDependent);
+ expect(instance.getFeatureType(dep2)).toEqual(instance.featureTypeDependent);
+ expect(instance.getDependentColumn()).toEqual(dep2);
+ expect(instance.getFeatureType(dep1)).toEqual(instance.getFeatureDefaultType(dep1));
+
+ // Dependent column should no longer be included as categorical
+ //expect(instance.getCatFeatures().find(el => el === dep2)).toEqual(undefined);
+ expect(instance.getCatFeatures()).not.toContain(dep2);
+
+ // Prediction type - should be set to default
+ expect(instance.state.predictionType).toEqual(instance.defaultPredictionType);
+ })
-// https://jestjs.io/docs/en/tutorial-async
-// jest mock is not working, not returning promise, must be improperly configured and/or
-// not setup correctly or something
+ // Test the methods used by the dialogs for text-based specification of
+ // categorical feature types
+ it('test text-based categorical-type specification', () => {
+ // Helper to simulate opening dialog, entering text and then accepting it
+ // cancel - pass true to cancel the input instead of accepting
+ function updateCatText(text, cancel = false) {
+ // Open the dialog
+ let openButton = shallowDom.find("#cat_features_text_input_open_button");
+ expect(openButton.length).toEqual(1);
+ openButton.at(0).simulate('click');
+ // Get the text input element
+ let textInput = shallowDom.find("#categorical_features_text_area_input");
+ expect(textInput.length).toEqual(1);
+ // Simulate text input event
+ let event = {target: {value: text}};
+ textInput.at(0).prop('onChange')(event); // stores text to state
+ textInput.at(0).prop('onBlur')(event); // stores text as raw to state
+ // Simulate button press
+ if(cancel)
+ shallowDom.find("#cat_features_user_text_cancel_button").at(0).simulate('click'); //uses state vars
+ else
+ shallowDom.find("#cat_features_user_text_accept_button").at(0).simulate('click'); //uses state vars
+ shallowDom.update();
+ }
-//jest.mock('../../utils/apiHelper');
-//import uploadDataset from '../../data/datasets/dataset/api';
+ // Close the cat feature text input dialog
+ function closeCatText() {
+ let button = shallowDom.find("#cat_features_user_text_cancel_button");
+ if(button.length > 0) {
+ button.at(0).simulate('click'); //uses state vars
+ }
+ }
-// cheating and just importing fake apiHelper directly - probably not recommended
-import { uploadFile } from '../../utils/__mocks__/apiHelper';
-describe('mock network request', () => {
- let store = mockStore(initialState);
- let testFileUpload;
+ //Reset feature types to dfault
+ setAllFeaturesToDefaults();
+
+ // Should all be default still after empty string
+ updateCatText("");
+ expect(errorModalIsShowing().result).toBe(false);
+ testFeatureTypes('default');
+
+ // Assign single feature (that's already categorical) via text.
+ // This shouldn't change anything because other features that default to categorical
+ // will stay as such.
+ updateCatText(catFeaturesOrig[0]);
+ expect(errorModalIsShowing().result).toBe(false);
+ testFeatureTypes('default');
+
+ // Change text to set all features as categorical, but hit cancel button.
+ // Shouldn't change feature assignments.
+ updateCatText(featureNames.join(), true /*simulate clicking cancel button*/);
+ expect(errorModalIsShowing().result).toBe(false);
+ testFeatureTypes('default');
+
+ // Assign all as categorical by passing string of all feature names
+ updateCatText(featureNames.join());
+ expect(errorModalIsShowing().result).toBe(false);
+ testFeatureTypes(instance.featureTypeCategorical);
+
+ // Assign just default-categorical again, and the one default-numeric feature
+ // should revert to numeric
+ updateCatText(featureNames.join()); // set all to type cat
+ updateCatText(catFeaturesOrig.join()); // leaves out those not cat by default
+ expect(errorModalIsShowing().result).toBe(false);
+ testFeatureTypes('default');
+
+ // Reset
+ setAllFeaturesToDefaults();
+
+ // Test with range - set all to categorical
+ updateCatText(featureNames[0]+"-"+featureNames[3]);
+ expect(errorModalIsShowing().result).toBe(false);
+ testFeatureTypes(instance.featureTypeCategorical);
+
+ // Test with smaller range. Should set all to categorical
+ instance.setAllFeatureTypes(instance.featureTypeOrdinal);
+ updateCatText(featureNames[0]+","+featureNames[1]+"-"+featureNames[2]+","+featureNames[3]);
+ expect(errorModalIsShowing().result).toBe(false);
+ testFeatureTypes(instance.featureTypeCategorical);
+
+ // Test with bad string. Should see error modal and unchanged feature types
+ setAllFeaturesToDefaults();
+ updateCatText("woogie woogie");
+ expect(errorModalIsShowing().result).toBe(true);
+ closeErrorModal();
+ closeCatText();
+ testFeatureTypes('default');
+
+ // Test with a bad range at begin
+ setAllFeaturesToDefaults();
+ updateCatText("frankenNoodle-"+featureNames[3]);
+ expect(errorModalIsShowing().result).toBe(true);
+ closeErrorModal();
+ closeCatText();
+ testFeatureTypes('default');
+
+ // Test with a bad range at end
+ setAllFeaturesToDefaults();
+ updateCatText(featureNames[0]+"-frankenNoodle");
+ expect(errorModalIsShowing().result).toBe(true);
+ closeErrorModal();
+ closeCatText();
+ testFeatureTypes('default');
+
+ // Test with setting dependent feature.
+ // The dependent feature should not change when its feature is entered in text input,
+ // and we should see error dialog.
+ instance.setFeatureType(featureNames[0], instance.featureTypeDependent);
+ updateCatText(catFeaturesOrig.join());
+ expect(errorModalIsShowing().result).toBe(true);
+ closeErrorModal();
+ closeCatText();
+ let expected = [...getFeatureTypesDefault()];
+ expected[0] = instance.featureTypeDependent;
+ testFeatureTypes(expected);
- beforeEach(() => {
- //testFileUpload = mount();
- testFileUpload = shallow().dive();
})
- const fakeDatasets = [
- {
- 'name': 'auto.csv',
- 'username': 'testuser',
- 'dependent_col' : 'class',
- 'categorical_features': '',
- 'ordinal_features': '',
- 'timestamp': Date.now(),
- },
- {
- 'name': 'iris.csv',
- 'username': 'testuser',
- 'dependent_col' : 'target_class',
- 'categorical_features': '',
- 'ordinal_features': '',
- 'timestamp': Date.now(),
+ // Test the methods used by the dialogs for text-based specification of
+ // ordinal feature types and ordinal ranking
+ it('test text-based ordinal-type specification', () => {
+ //Reset feature types to dfault
+ setAllFeaturesToDefaults();
+
+
+ // Simulate entering text and then accepting it.
+ // cancel - pass true to simulate clicking the cancel button in text entry box.
+ function updateOrdText(text, cancel = false) {
+ // Open the dialog
+ let openButton = shallowDom.find("#ord_features_text_input_open_button");
+ expect(openButton.length).toEqual(1);
+ openButton.at(0).simulate('click');
+ // Get the text input element
+ let textInput = shallowDom.find("#ordinal_features_text_area_input");
+ expect(textInput.length).toEqual(1);
+ // Simulate text change event
+ let event = {target: {value: text}};
+ textInput.at(0).prop('onChange')(event); // stores text to state
+ shallowDom.update();
+ if(cancel)
+ shallowDom.find("#ordinal_features_user_text_cancel_button").at(0).simulate('click'); //uses state vars to process text
+ else
+ shallowDom.find("#ordinal_features_user_text_accept_button").at(0).simulate('click'); //uses state vars to process text
+ shallowDom.update();
}
- ];
-
- it('testing promise for successful case, proper dependent_col', () => {
- expect.assertions(1);
- //return uploadDataset(fakeDataset).then(data => expect(data.name).toEqual('iris.csv'));
- //return uploadFile('fakeurl').then(data => expect(data.name).toEqual('iris.csv'));
-
- // on successful upload, change window location/redirect the page, uses error
- // flag in server response to control logic for UI to display message on error
- // or page redirection on success, not sure how to get at those pieces with
- // this testing framework. DOM does not seem to be updating correctly
- return uploadFile(fakeDatasets[0])
- .then(data => expect(data.id).toEqual(7654321));
- })
-
- it('testing promise for unsuccessful case, improper dependent_col', () => {
- expect.assertions(1);
- return uploadFile(fakeDatasets[1])
- .catch(e => {
- expect(e).toEqual({'error': 'dependent_col: target_class invalid'});
-
- // fake setting state, normally occurs in upload handler function in
- // FileUpload component after promise making actual upload resolves
-
- // testFileUpload.setState({
- // errorResp: e,
- // serverResp: e
- // });
-
- // manually setting state is not working correctly?, popup open prop should be set
- // to 'true' in case of error response, not sure if this is incorrect method
- // of testing how UI should look based on react component state
-
- // testFileUpload = shallow().dive();
- // testFileUpload.update();
- // let popup = testFileUpload.find('Popup').at(0);
- // console.log('popup props', popup.props());
- });
- })
-})
-// go through basic file upload flow - select file & info, upload file
-// cover two scenarios - upload success and failure
-
-// this test examines styling based on mock user input
-describe('basic file upload flow', () => {
-
- let store = mockStore(initialState);
- let testFileUpload;
+ function closeOrdText() {
+ let button = shallowDom.find("#ordinal_features_user_text_cancel_button");
+ if(button.length > 0){
+ button.at(0).simulate('click'); //uses state vars to process text
+ }
+ }
- beforeEach(() => {
- // https://github.com/airbnb/enzyme/issues/1002
- testFileUpload = shallow().dive();
- // having some trouble using mount, updating state, & checking for expected UI changes
- //testFileUpload = mount();
+ // Should all be default still after empty string
+ setAllFeaturesToDefaults();
+ updateOrdText("");
+ expect(errorModalIsShowing().result).toBe(false);
+ testFeatureTypes('default');
+
+ // Test invalid feature name. Shouldn't change feature assignments
+ setAllFeaturesToDefaults();
+ updateOrdText("knights,ni");
+ expect(errorModalIsShowing().result).toBe(true);
+ closeErrorModal();
+ closeOrdText();
+ testFeatureTypes('default');
+
+ // Test valid feature name, but invalid values
+ setAllFeaturesToDefaults();
+ updateOrdText("col1,ni");
+ expect(errorModalIsShowing().result).toBe(true);
+ closeErrorModal();
+ closeOrdText();
+ testFeatureTypes('default');
+
+ // Test canceling input. Feature types should not change.
+ setAllFeaturesToDefaults();
+ updateOrdText("col1,ni", true);
+ expect(errorModalIsShowing().result).toBe(false);
+ testFeatureTypes('default');
+
+ // Test valid feature name and values,
+ // test that ordinal value is correct in state
+ setAllFeaturesToDefaults();
+ updateOrdText("col1,one,two,three");
+ expect(errorModalIsShowing().result).toBe(false);
+ let expected = [...getFeatureTypesDefault()];
+ expected[0] = instance.featureTypeOrdinal;
+ testFeatureTypes(expected);
+ let ords = { col1: ['one','two','three']};
+ expect(instance.state.ordinalFeaturesObject).toEqual(ords);
+
+ // Test two valid features names and values
+ setAllFeaturesToDefaults();
+ updateOrdText("col1,one,two,three\ncol2,bird,cat,dog");
+ expect(errorModalIsShowing().result).toBe(false);
+ expected = [...getFeatureTypesDefault()];
+ expected[0] = instance.featureTypeOrdinal;
+ expected[1] = instance.featureTypeOrdinal;
+ testFeatureTypes(expected);
+ ords = { col1: ['one','two','three'], col2: ['bird','cat','dog']};
+ expect(instance.state.ordinalFeaturesObject).toEqual(ords);
+
+ // Test one valid feature and values, one invalid featuare
+ setAllFeaturesToDefaults();
+ updateOrdText("col1,one,two,three\nunassumingduck,bird,cat,dog");
+ expect(errorModalIsShowing().result).toBe(true);
+ closeErrorModal();
+ closeOrdText();
+ expect(instance.state.ordinalFeaturesObject).toEqual({});
+ testFeatureTypes('default');
+
+ // Test two valid features, one with valid values, one with invalid values
+ setAllFeaturesToDefaults();
+ updateOrdText("col1,one,two,three\ncol2,bird,cat,fish");
+ expect(errorModalIsShowing().result).toBe(true);
+ closeErrorModal();
+ closeOrdText();
+ expect(instance.state.ordinalFeaturesObject).toEqual({});
+ testFeatureTypes('default');
+
+ // Test valid feature name but that's set as dependent feature.
+ // Expect an error and dependent feature not to be changed.
+ setAllFeaturesToDefaults();
+ instance.setFeatureType(featureNames[0], instance.featureTypeDependent);
+ updateOrdText("col1,one,two,three");
+ expect(errorModalIsShowing().result).toBe(true);
+ closeErrorModal();
+ closeOrdText();
+ expected = [...getFeatureTypesDefault()];
+ expected[0] = instance.featureTypeDependent;
+ testFeatureTypes(expected);
+
+ // Test setting a feature to ord (with a different ranking than above), and then clearing it by
+ // changing input text to omit it.
+ setAllFeaturesToDefaults();
+ updateOrdText("col1,one,two,three\ncol2,bird,cat,dog");
+ expect(errorModalIsShowing().result).toBe(false);
+ updateOrdText("col2,cat,dog,bird");
+ expect(errorModalIsShowing().result).toBe(false);
+ ords = { col2: ['cat','dog','bird']};
+ expect(instance.state.ordinalFeaturesObject).toEqual(ords);
+ expected = [...getFeatureTypesDefault()];
+ expected[1] = instance.featureTypeOrdinal;
+ testFeatureTypes(expected);
+
+ // Test setting feature to type ordinal via dropdown in dataset preview,
+ // and then check that string in feature specification input box is correct.
+ //
+ // Get the dropdown input element for
+ setAllFeaturesToDefaults();
+ let featureDrop = shallowDom.find("#featureTypeDropdown-0");
+ expect(featureDrop.length).toEqual(1);
+ let index = 0;
+ // Simulate setting its value
+ featureDrop.at(index).simulate('change',
+ { target: { value: 'testval', name: 'testname' } },
+ {value: instance.featureTypeOrdinal, customindexid: index}
+ );
+ // Expect the feature to be type ordinal now
+ expected = [...getFeatureTypesDefault()];
+ expected[index] = instance.featureTypeOrdinal;
+ testFeatureTypes(expected);
+ // Expect the default/orig ordering of values
+ let ordsArr = ['one','three','two'];
+ ords = { col1: ordsArr };
+ expect(instance.state.ordinalFeaturesObject).toEqual(ords);
+ // Expect the text string in the ordinal specification box to match
+ let openButton = shallowDom.find("#ord_features_text_input_open_button");
+ expect(openButton.length).toEqual(1);
+ openButton.at(0).simulate('click');
+ let ordTextInput = shallowDom.find("#ordinal_features_text_area_input");
+ expect(ordTextInput.length).toEqual(1);
+ let expectedString = featureNames[index] + ',' + ordsArr.join() + '\n';
+ let value = ordTextInput.at(index).props().value;
+ expect(value).toBe(expectedString);
+
+ // cleanup double-check
+ closeErrorModal();
+ closeOrdText();
})
- // the intended behavior of this component is to hide the fields to enter info
- // about the file being uploaded until a file with a filename has been selected
- it('check UI form is hidden w/o a file selection', () => {
- let formBody = testFileUpload.find('#file-upload-form-input-area');
+ // Test UI elements directly to see that they hold the right values
+ //
+ it('test other UI elements', () => {
+ // Reset feature types to default AND clears dependent column to undefined
+ setAllFeaturesToDefaults();
+
+ // Verify various dataset preview table values
+
+ // Header row
+ featureNames.forEach( (feature, index) => {
+ // Verify column header label
+ let label = shallowDom.find("#table_header_label_" + feature);
+ expect(label.length).toEqual(1);
+ // Empirically, the text assigned to the Segment component is
+ // in the second child object.
+ expect(label.at(0).props().children[1]).toEqual(featureNames[index]);
+
+ // Verify correct feature types in dropdowns
+ let drop = shallowDom.find("#featureTypeDropdown-" + index.toString());
+ expect(drop.length).toEqual(1);
+ expect(drop.at(0).props().value).toEqual(getFeatureTypesDefault()[index]);
+ } )
+
+ // Target/Dependent column
+ //
+ // Set column via dropdown
+ let targetDrop = shallowDom.find("#target_dropdown");
+ expect(targetDrop.length).toEqual(1);
+ // Simulate setting its value
+ let index = 2;
+ targetDrop.at(0).simulate('change',
+ { target: { value: 'testval', name: 'testname' } },
+ {value: featureNames[index]}
+ );
+ let expected = getFeatureTypesDefault();
+ expected[index] = instance.featureTypeDependent;
+ testFeatureTypes(expected);
+
+ // Expect a label of 'Target' instead of a dropdown in the data table preview
+ let targetLabel = shallowDom.find("#featureTypeTargetLabel-" + index.toString());
+ expect(targetLabel.length).toEqual(1);
+
+ // Verify the data shown in table preview
+ for( let row=0; row < dataByFeature[0].length; row++) {
+ featureNames.forEach( (feature, index) => {
+ let id = '#data_table_prev_' + row.toString() + '_' + feature;
+ let data = shallowDom.find(id);
+ // Empirically, TableCell component has one child that holds value
+ expect(data.length).toEqual(1);
+ expect(data.at(0).props().children.toString()).toEqual(dataByFeature[index][row].toString());
+ })
+ }
+
+ // Prediction type dropdown
+ // Should default to default prediction type
+ let predDrop = shallowDom.find("#prediction_type_dropdown");
+ expect(predDrop.length).toEqual(1);
+ //TODO - figure out how to check currenty value of dropdown. It doesn't
+ // work to check predDrop.at(0).props().value with this type of dropdown
+ // which is part of a Field.
+ //console.log('prepDrop ', predDrop.at(0).props().value);
+ //
+ // Set to type regression via event handler
+ let newPredType = 'regression';
+ predDrop.at(0).simulate('change',
+ { target: { value: 'testval', name: 'testname' } },
+ {value: newPredType}
+ );
+ // Check that it's changed
+ expect(instance.state.predictionType).toEqual(newPredType);
- // check for CSS style which hides form
- expect(formBody.hasClass('file-upload-form-hide-inputs')).toEqual(true);
- expect(formBody.hasClass('file-upload-form-show-inputs')).toEqual(false);
})
- it('test selecting file and displaying UI form after file selection', () => {
- // fake user submission by entering info directly into component state
- let fileName = 'iris.csv';
- let testFile = {
- name: fileName
- };
- let depCol = 'class';
- let catFeatures = '';
- let ordFeatures = {};
- let formBody = testFileUpload.find('#file-upload-form-input-area');
-
- // check for CSS style which hides form
- expect(formBody.hasClass('file-upload-form-hide-inputs')).toEqual(true);
+ // Test the button/menu that sets all features to particular type
+ // The 'Set-all-to' button.
+ it('test set-all-features-to menu', () => {
+ setAllFeaturesToDefaults();
+
+ // Click the 'set-all-to' button and check that menu is open
+ let button = shallowDom.find("#set_all_to_button");
+ expect(button.length).toEqual(1);
+ button.at(0).simulate('click');
+ let menu = shallowDom.find("#set_all_to_menu");
+ expect(menu.length).toEqual(1);
+ expect(menu.at(0).props().open).toEqual(true);
+
+ // Test set all to categorical
+ let catMenu = shallowDom.find("#set_all_to_menu_categorical");
+ expect(catMenu.length).toEqual(1);
+ catMenu.at(0).simulate('click');
+ testFeatureTypes(instance.featureTypeCategorical);
+
+ // Test set all to ordinal
+ let ordMenu = shallowDom.find("#set_all_to_menu_ordinal");
+ expect(ordMenu.length).toEqual(1);
+ ordMenu.at(0).simulate('click');
+ testFeatureTypes(instance.featureTypeOrdinal);
+
+ // Test set all to defaults
+ let defMenu = shallowDom.find("#set_all_to_menu_default");
+ expect(defMenu.length).toEqual(1);
+ defMenu.at(0).simulate('click');
+ testFeatureTypes('default');
+
+ })
- // essentially fake user input
- testFileUpload.setState({
- selectedFile: testFile,
- dependentCol: depCol,
- catFeatures: catFeatures,
- ordinalFeatures: ordFeatures
+ // Test the routine that preapes data for uploading.
+ // Does NOT test the actual uploading process, since
+ // that's an integration test.
+ it('test generate file data', () => {
+ setAllFeaturesToDefaults();
+
+ // Set the dependent column
+ instance.setFeatureType(featureNames[depColumnIndex], instance.featureTypeDependent);
+ // Change the prediction type
+ let predType = 'regression';
+ instance.setState( {predictionType: predType});
+ shallowDom.update();
+ // Set first feature to ordinal
+ instance.setFeatureType(featureNames[0], instance.featureTypeOrdinal);
+
+ let formData = instance.generateFileData();
+ // Check results
+ expect(formData.errorResp).toBeUndefined();
+ expect(errorModalIsShowing().result).toEqual(false);
+ // Get the metadata values
+ let result = JSON.parse( formData.get('_metadata') );
+ // Ordinals
+ let ords = {};
+ ords[featureNames[0]] = ['one','three','two'];
+ expect(result['ordinal_features']).toEqual(ords);
+ // Categoricals
+ let cats= [ featureNames[1], featureNames[3] ];
+ expect(result.categorical_features).toEqual(cats);
+ // Prediction type
+ expect(result.prediction_type).toEqual(predType);
+ // Dependent column
+ expect(result.dependent_col).toEqual(featureNames[depColumnIndex]);
+ // Filename
+ expect(result.name).toEqual(testFilename);
+
+ // Test with bad prediction type.
+ instance.setState( {
+ predictionType: 'nice try buddy!',
+ uploadButtonDisabled: false //hack
});
- //expect(testFileUpload.state('selectedFile')).toBeDefined();
- //expect(testFileUpload.state('selectedFile').name).toEqual('iris.csv');
- // force rerender - component is wrapped in redux stuff (Provider), don't think it will rerender
- //testFileUpload.update();
-
- // not sure but must be getting new copy of enzyme/react node with expected
- // CSS class after state changes by re-assigning variable
- formBody = testFileUpload.find('#file-upload-form-input-area');
-
- // user info is entered in form, check if form input area is visible
- // look for css styling to display form
- expect(formBody.hasClass('file-upload-form-show-inputs')).toEqual(true);
+ shallowDom.update();
+ formData = instance.generateFileData();
+ expect(formData.errorResp).toBeDefined();
+
+ // Test again with the handleUpload method to test that error gets caught and handled.
+ // Should get error modal.
+ instance.handleUpload();
+ shallowDom.update();
+ expect(errorModalIsShowing().result).toEqual(true);
+ closeErrorModal();
+
+ // Test with undefined dependent column
+ setAllFeaturesToDefaults(); //clears dep. column
+ formData = instance.generateFileData();
+ expect(formData.errorResp).toBeDefined();
+
+ // Test again with handleUpload. Should get error modal
+ instance.setState( {
+ uploadButtonDisabled: false //hack for check that's done in handleUpload
+ });
+ shallowDom.update();
+ instance.handleUpload();
+ shallowDom.update();
+ expect(errorModalIsShowing().result).toEqual(true);
+ closeErrorModal();
})
-
-})
+})
\ No newline at end of file
diff --git a/lab/webapp/src/components/FileUpload/index.js b/lab/webapp/src/components/FileUpload/index.js
index c82837b24..3604dfb8c 100644
--- a/lab/webapp/src/components/FileUpload/index.js
+++ b/lab/webapp/src/components/FileUpload/index.js
@@ -62,6 +62,8 @@ class FileUpload extends Component {
/** Special type to mark the dependent column.
* It's not properly a feature, but use same terminology for consistency. */
get featureTypeDependent() { return 'dependent'; }
+ //Type to return in case of errors
+ get featureTypeDefault() {return this.featureTypeNumeric; }
/**
* FileUpload reac component - UI form for uploading datasets
@@ -110,6 +112,7 @@ class FileUpload extends Component {
this.parseFeatureToken = this.parseFeatureToken.bind(this);
this.initFeatureTypeDefaults = this.initFeatureTypeDefaults.bind(this);
this.getDependentColumn = this.getDependentColumn.bind(this);
+ this.clearDependentFeature = this.clearDependentFeature.bind(this);
this.getElapsedTime = this.getElapsedTime.bind(this);
this.getOridinalRankingDialog = this.getOridinalRankingDialog.bind(this);
this.getOrdinalFeaturesUserTextModal = this.getOrdinalFeaturesUserTextModal.bind(this);
@@ -181,7 +184,7 @@ class FileUpload extends Component {
ordinalFeaturesUserTextModalOpen: false,
/** {object} Object used as dictionary to track the features designated as ordinal by user via dataset preview UI.
* key: feature name from dataPreview
- * value: string-array holding possibly-ordered values for the feature.
+ * value: string-array holding possibly-ordered unique values for the feature.
* Will be empty object if none defined.
* Gets updated with new order as user orders them using the UI in dataset preview.
* Using objects as dictionary: https://pietschsoft.com/post/2015/09/05/javascript-basics-how-to-create-a-dictionary-with-keyvalue-pairs
@@ -198,6 +201,8 @@ class FileUpload extends Component {
ordinalFeatureToRankValues: [],
allFeaturesMenuOpen: false,
predictionType: this.defaultPredictionType,
+ /** {string} Used in unit testing to test state retrieval */
+ testStateValue: 'foobar'
}
}
@@ -242,6 +247,11 @@ class FileUpload extends Component {
}
}
+ /** Simple test method for unit testing */
+ instanceTest(){
+ return 'foobar';
+ }
+
/** Helper routine for debugging. Get elapsed time in sec from
* either init or from the previous call to this method.
*/
@@ -448,7 +458,8 @@ handleCatFeaturesUserTextCancel() {
//window.console.log('preview of uploaded data: ', dataPrev);
// after uploading a dataset request new list of datasets to update the page
} else {
- window.console.log('no file available');
+ window.console.log('generateFileDate: no file available');
+ return { errorResp: "No file is available." };
}
return data;
@@ -488,6 +499,11 @@ handleCatFeaturesUserTextCancel() {
this.setState({processingFileForPreview: false});
}
+ /** Stub method that's mocked in unit testing */
+ handleSelectedFileCompletedStub(){
+ //do nothing
+ }
+
/**
* Event handler for selecting files, takes user file from html file input, stores
* selected file in component react state, generates file preview and stores that
@@ -521,6 +537,10 @@ handleCatFeaturesUserTextCancel() {
if(this.isDevBuild)
console.log( this.getElapsedTime() + " - done with initDatasetPreview.");
+
+ //Call this method for use in unit testing, to know we've completed successfully here,
+ // and can inspect the new state
+ this.handleSelectedFileCompletedStub();
}
};
@@ -528,8 +548,6 @@ handleCatFeaturesUserTextCancel() {
if(files && files[0]) {
// immediately try to get dataset preview on file input html element change
// need to be mindful of garbage data/files
- //console.log(typeof event.target.files[0]);
- //console.log(event.target.files[0]);
let uploadFile = files[0]
let fileExt = uploadFile.name.split('.').pop();
@@ -595,7 +613,6 @@ handleCatFeaturesUserTextCancel() {
this.setState({uploadButtonDisabled:true});
const { uploadDataset } = this.props;
-
// only attempt upload if there is a selected file with a filename
if(this.state.selectedFile && this.state.selectedFile.name) {
let data = this.generateFileData(); // should be FormData
@@ -667,13 +684,13 @@ handleCatFeaturesUserTextCancel() {
/**
* For the passed feature name, return its type
* @param {string} feature
- * @returns {string} feature type (ordinal, categorical, numeric)
+ * @returns {string} feature type (as returned by one of gettors: featureTypeOrdinal, ...Categorical, ...Numeric, ...Dependent)
*/
getFeatureType(feature) {
let i = this.getFeatureIndex(feature);
if( i === -1 ) {
console.log("ERROR: unrecognized feature: " + feature);
- return this.featureTypeNumeric;
+ return this.featureTypeDefault;
}
return this.state.featureType[i];
}
@@ -749,7 +766,7 @@ handleCatFeaturesUserTextCancel() {
getFeatureDefaultType(feature) {
if( !this.validateFeatureName(feature)) {
console.log("Cannot get default type for unrecognized feature: " + feature);
- return this.featureTypeNumeric;
+ return this.featureTypeDefault;
}
return this.state.featureTypeDefaults[feature];
}
@@ -757,8 +774,9 @@ handleCatFeaturesUserTextCancel() {
/**
* Set the feature type for all features in the data.
* Does NOT change type of column/feature that is assigned as dependent column.
- * @param {string} type - one of [featureTypeNumeric, featureTypeCategorical, featureTypeOrdinal, 'autoDefault'],
- * where 'autoDefault' will set each feature type based on analysis of each feature's values
+ * Does NOT change features with any non-numeric values to type numeric. They are left unchanged.
+ * @param {string} type - one of either [featureTypeNumeric, featureTypeCategorical, featureTypeOrdinal, 'autoDefault'],
+ * or 'autoDefault' which will set each feature type based on analysis of each feature's values
*/
setAllFeatureTypes(type) {
if(this.state.datasetPreview == null) {
@@ -814,11 +832,10 @@ handleCatFeaturesUserTextCancel() {
return;
}
- // Handle dependent column type
+ // Handle setting dependent column type
if( type == this.featureTypeDependent) {
//Clear the currently-assigned dependent column if there is one
- let currentDep = this.getDependentColumn();
- currentDep !== undefined && this.setFeatureType(currentDep, this.getFeatureDefaultType(currentDep));
+ this.clearDependentFeature();
}
// Handle ordinal type
@@ -916,16 +933,22 @@ handleCatFeaturesUserTextCancel() {
/** Clear any features that have been specified as type ordinal, along with any related data,
* and set them to type auto-determined default type.
- * Does NOT clear the storage of previous ordinal features, so you can still recover previous
- * settings even when changing from user text input.
+ * @param {boolean} clearPrev - false by default, to NOT clear the storage of previous ordinal features,
+ * so you can still recover previous settings even when changing from user text input. 'True' will
+ * clear the previous values (for testing).
*/
- ordinalFeaturesClearToDefault() {
+ ordinalFeaturesClearToDefault( clearPrev = false) {
for(var feature in this.state.ordinalFeaturesObject) {
this.setFeatureType(feature, this.getFeatureDefaultType(feature));
}
this.setState({
ordinalFeaturesObject: {},
})
+ if(clearPrev) {
+ this.setState({
+ ordinalFeaturesObjectPrev: {},
+ })
+ }
}
/** From state, convert the lists of unique values for ordinal features into a string with
@@ -1025,13 +1048,16 @@ handleCatFeaturesUserTextCancel() {
* @returns {null}
*/
ordinalFeaturesUserTextIngest() {
+ // Get the lines before clearing ordinal features to default. Otherwise can
+ // end up clearing ordinalFeaturesUserText in some cases.
+ let lines = this.state.ordinalFeaturesUserText.split(/\r?\n/);
this.ordinalFeaturesClearToDefault();
//Process each line individually
- this.state.ordinalFeaturesUserText.split(/\r?\n/).map((line) => {
- if(line === "")
- return;
- let ordObj = this.ordinalFeaturesUserTextParse(line);
- this.setFeatureType(ordObj.feature, this.featureTypeOrdinal, ordObj.values);
+ lines.map((line) => {
+ if(line != "") {
+ let ordObj = this.ordinalFeaturesUserTextParse(line);
+ this.setFeatureType(ordObj.feature, this.featureTypeOrdinal, ordObj.values);
+ }
})
//console.log("ingest: ordinals: ");
//console.log(this.state.ordinalFeaturesObject);
@@ -1091,8 +1117,10 @@ handleCatFeaturesUserTextCancel() {
dataPrev.meta.fields.map((field, i) => {
//Assign dropdown items for setting field type, or just 'Target' label for columns that's designated as target
let fieldTypeItem = ( field === this.getDependentColumn() ?
- Target :
+ Target
+ :
- {field}
+ {field} {fieldTypeItem}
{/*Return a segment with 'rank'button, or null, based on field type*/}
{this.getDataTableOrdinalRankButton(field)}
@@ -1135,9 +1163,9 @@ handleCatFeaturesUserTextCancel() {
{i+1}
{dataPrev.meta.fields.map(field => {
- let tempKey = i + field;
+ let key_id = 'data_table_prev_' + i.toString() +'_' + field;
return (
-
+
{row[field]}
)
@@ -1190,6 +1218,14 @@ handleCatFeaturesUserTextCancel() {
return result;
}
+ /** Clear the depedent feature selection and return it to
+ * its default type.
+ */
+ clearDependentFeature() {
+ let currentDep = this.getDependentColumn();
+ currentDep !== undefined && this.setFeatureType(currentDep, this.getFeatureDefaultType(currentDep));
+ }
+
/**
* Small helper to get an array of features that have been assigned
* to type 'categorical'
@@ -1304,6 +1340,7 @@ handleCatFeaturesUserTextCancel() {
-