remove UI spec and refactor ui_spec.order to input_field_order property
This commit is contained in:
parent
deb37d9c5c
commit
6639e0ff73
|
@ -1141,16 +1141,12 @@ class OptionStringOutput(Output):
|
|||
"""
|
||||
options: typing.Union[list, None] = None
|
||||
|
||||
@dataclasses.dataclass
|
||||
class UISpec:
|
||||
order: typing.Union[list, None] = None
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ModelSpec:
|
||||
model_id: str
|
||||
model_title: str
|
||||
userguide: str
|
||||
ui_spec: UISpec
|
||||
input_field_order: list[str]
|
||||
inputs: typing.Iterable[Input]
|
||||
outputs: typing.Iterable[Output]
|
||||
args_with_spatial_overlap: dict
|
||||
|
@ -1213,16 +1209,14 @@ def build_model_spec(model_spec):
|
|||
for argkey, argspec in model_spec['args'].items()]
|
||||
outputs = [
|
||||
build_output_spec(argkey, argspec) for argkey, argspec in model_spec['outputs'].items()]
|
||||
ui_spec = UISpec(
|
||||
order=model_spec['ui_spec']['order'])
|
||||
return ModelSpec(
|
||||
model_id=model_spec['model_id'],
|
||||
model_title=model_spec['model_title'],
|
||||
userguide=model_spec['userguide'],
|
||||
aliases=model_spec['aliases'],
|
||||
ui_spec=ui_spec,
|
||||
inputs=inputs,
|
||||
outputs=outputs,
|
||||
input_field_order=model_spec['ui_spec']['order'],
|
||||
args_with_spatial_overlap=model_spec.get('args_with_spatial_overlap', None))
|
||||
|
||||
|
||||
|
|
|
@ -27,6 +27,6 @@ MODEL_SPEC = spec.ModelSpec(inputs=[
|
|||
model_id='',
|
||||
model_title='',
|
||||
userguide='',
|
||||
ui_spec=spec.UISpec(),
|
||||
input_field_order=[],
|
||||
args_with_spatial_overlap={}
|
||||
)
|
||||
|
|
|
@ -9,6 +9,6 @@ MODEL_SPEC = spec.ModelSpec(
|
|||
model_id='',
|
||||
model_title='',
|
||||
userguide='',
|
||||
ui_spec=spec.UISpec(),
|
||||
input_field_order=[],
|
||||
args_with_spatial_overlap={}
|
||||
)
|
||||
|
|
|
@ -9,6 +9,6 @@ MODEL_SPEC = spec.ModelSpec(inputs=[
|
|||
model_id='',
|
||||
model_title='',
|
||||
userguide='',
|
||||
ui_spec=spec.UISpec(),
|
||||
input_field_order=[],
|
||||
args_with_spatial_overlap={}
|
||||
)
|
||||
|
|
|
@ -6,6 +6,6 @@ MODEL_SPEC = spec.ModelSpec(inputs=[
|
|||
model_id='',
|
||||
model_title='',
|
||||
userguide='',
|
||||
ui_spec=spec.UISpec(),
|
||||
input_field_order=[],
|
||||
args_with_spatial_overlap={}
|
||||
)
|
||||
|
|
|
@ -13,6 +13,6 @@ MODEL_SPEC = spec.ModelSpec(inputs=[
|
|||
model_id='',
|
||||
model_title='',
|
||||
userguide='',
|
||||
ui_spec=spec.UISpec(),
|
||||
input_field_order=[],
|
||||
args_with_spatial_overlap={}
|
||||
)
|
||||
|
|
|
@ -7,6 +7,6 @@ MODEL_SPEC = SimpleNamespace(inputs=[
|
|||
model_id='',
|
||||
model_title='',
|
||||
userguide='',
|
||||
ui_spec=spec.UISpec(),
|
||||
input_field_order=[],
|
||||
args_with_spatial_overlap={}
|
||||
)
|
||||
|
|
|
@ -7,6 +7,6 @@ MODEL_SPEC = spec.ModelSpec(inputs=[
|
|||
model_id='',
|
||||
model_title='',
|
||||
userguide='',
|
||||
ui_spec=spec.UISpec(),
|
||||
input_field_order=[],
|
||||
args_with_spatial_overlap={}
|
||||
)
|
||||
|
|
|
@ -121,7 +121,7 @@ class ValidateModelSpecs(unittest.TestCase):
|
|||
"""MODEL_SPEC: test each spec meets the expected pattern."""
|
||||
|
||||
required_keys = {'model_id', 'model_title', 'userguide',
|
||||
'aliases', 'inputs', 'ui_spec', 'outputs'}
|
||||
'aliases', 'inputs', 'input_field_order', 'outputs'}
|
||||
for model_id, pyname in model_id_to_pyname.items():
|
||||
model = importlib.import_module(pyname)
|
||||
|
||||
|
@ -137,10 +137,9 @@ class ValidateModelSpecs(unittest.TestCase):
|
|||
set(model.MODEL_SPEC.args_with_spatial_overlap).issubset(
|
||||
{'spatial_keys', 'different_projections_ok'}))
|
||||
|
||||
self.assertIsInstance(model.MODEL_SPEC.ui_spec, spec.UISpec)
|
||||
self.assertIsInstance(model.MODEL_SPEC.ui_spec.order, list)
|
||||
self.assertIsInstance(model.MODEL_SPEC.input_field_order, list)
|
||||
found_keys = set()
|
||||
for group in model.MODEL_SPEC.ui_spec.order:
|
||||
for group in model.MODEL_SPEC.input_field_order:
|
||||
self.assertIsInstance(group, list)
|
||||
for key in group:
|
||||
self.assertIsInstance(key, str)
|
||||
|
|
|
@ -361,7 +361,7 @@ class TestMetadataFromSpec(unittest.TestCase):
|
|||
model_title='Urban Nature Access',
|
||||
userguide='',
|
||||
aliases=[],
|
||||
ui_spec={},
|
||||
input_field_order=[],
|
||||
inputs={},
|
||||
args_with_spatial_overlap={},
|
||||
outputs=output_spec
|
||||
|
|
|
@ -45,7 +45,7 @@ class EndpointFunctionTests(unittest.TestCase):
|
|||
self.assertEqual(
|
||||
set(spec),
|
||||
{'model_id', 'model_title', 'userguide', 'aliases',
|
||||
'ui_spec', 'args_with_spatial_overlap', 'args', 'outputs'})
|
||||
'input_field_order', 'args_with_spatial_overlap', 'args', 'outputs'})
|
||||
|
||||
def test_get_invest_validate(self):
|
||||
"""UI server: get_invest_validate endpoint."""
|
||||
|
|
|
@ -66,8 +66,7 @@ class UsageLoggingTests(unittest.TestCase):
|
|||
}
|
||||
|
||||
model_spec = spec.ModelSpec(
|
||||
model_id='', model_title='', userguide=None,
|
||||
aliases=None, ui_spec=spec.UISpec(order=[]),
|
||||
model_id='', model_title='', userguide=None, aliases=None,
|
||||
inputs=[
|
||||
spec.SingleBandRasterInput(id='raster', band=spec.Input()),
|
||||
spec.VectorInput(id='vector', geometries={}, fields={}),
|
||||
|
@ -76,6 +75,7 @@ class UsageLoggingTests(unittest.TestCase):
|
|||
spec.VectorInput(id='blank_vector_path', geometries={}, fields={})
|
||||
],
|
||||
outputs={},
|
||||
input_field_order=[],
|
||||
args_with_spatial_overlap=None)
|
||||
|
||||
output_logfile = os.path.join(self.workspace_dir, 'logfile.txt')
|
||||
|
|
|
@ -22,7 +22,6 @@ from natcap.invest import spec
|
|||
from natcap.invest.spec import (
|
||||
u,
|
||||
ModelSpec,
|
||||
UISpec,
|
||||
Input,
|
||||
FileInput,
|
||||
CSVInput,
|
||||
|
@ -40,14 +39,12 @@ from natcap.invest.spec import (
|
|||
|
||||
gdal.UseExceptions()
|
||||
|
||||
def ui_spec_with_defaults(order=[]):
|
||||
return UISpec(order=order)
|
||||
|
||||
def model_spec_with_defaults(model_id='', model_title='', userguide='', aliases=None,
|
||||
ui_spec=ui_spec_with_defaults(), inputs={}, outputs={},
|
||||
inputs={}, outputs={}, input_field_order=[],
|
||||
args_with_spatial_overlap=[]):
|
||||
return ModelSpec(model_id=model_id, model_title=model_title, userguide=userguide,
|
||||
aliases=aliases, ui_spec=ui_spec, inputs=inputs, outputs=outputs,
|
||||
aliases=aliases, inputs=inputs, outputs=outputs,
|
||||
input_field_order=input_field_order,
|
||||
args_with_spatial_overlap=args_with_spatial_overlap)
|
||||
|
||||
def number_input_spec_with_defaults(id='', units=u.none, expression='', **kwargs):
|
||||
|
|
|
@ -37,7 +37,6 @@ class InvestTab extends React.Component {
|
|||
activeTab: 'setup',
|
||||
modelSpec: null, // MODEL_SPEC dict with all keys except MODEL_SPEC.args
|
||||
argsSpec: null, // MODEL_SPEC.args, the immutable args stuff
|
||||
uiSpec: null,
|
||||
userTerminated: false,
|
||||
executeClicked: false,
|
||||
tabStatus: '',
|
||||
|
@ -74,13 +73,10 @@ class InvestTab extends React.Component {
|
|||
}
|
||||
}
|
||||
try {
|
||||
const {
|
||||
args, ui_spec, ...model_spec
|
||||
} = await getSpec(job.modelID);
|
||||
const { args, ...model_spec } = await getSpec(job.modelID);
|
||||
this.setState({
|
||||
modelSpec: model_spec,
|
||||
argsSpec: args,
|
||||
uiSpec: ui_spec,
|
||||
}, () => { this.switchTabs('setup'); });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
@ -211,7 +207,6 @@ class InvestTab extends React.Component {
|
|||
activeTab,
|
||||
modelSpec,
|
||||
argsSpec,
|
||||
uiSpec,
|
||||
executeClicked,
|
||||
tabStatus,
|
||||
showErrorModal,
|
||||
|
@ -310,7 +305,7 @@ class InvestTab extends React.Component {
|
|||
userguide={modelSpec.userguide}
|
||||
modelID={modelID}
|
||||
argsSpec={argsSpec}
|
||||
uiSpec={uiSpec}
|
||||
inputFieldOrder={modelSpec.input_field_order}
|
||||
argsInitValues={argsValues}
|
||||
investExecute={this.investExecute}
|
||||
sidebarSetupElementId={sidebarSetupElementId}
|
||||
|
|
|
@ -35,7 +35,7 @@ const { logger } = window.Workbench;
|
|||
* Values initialize with either a complete args dict, or with empty/default values.
|
||||
*
|
||||
* @param {object} argsSpec - an InVEST model's MODEL_SPEC.args
|
||||
* @param {object} uiSpec - the model's UI Spec.
|
||||
* @param {object} inputFieldOrder - the order in which to display the input fields.
|
||||
* @param {object} argsDict - key: value pairs of InVEST model arguments, or {}.
|
||||
*
|
||||
* @returns {object} to destructure into two args,
|
||||
|
@ -45,11 +45,11 @@ const { logger } = window.Workbench;
|
|||
* {object} argsDropdownOptions - stores lists of dropdown options for
|
||||
* args of type 'option_string'.
|
||||
*/
|
||||
function initializeArgValues(argsSpec, uiSpec, argsDict) {
|
||||
function initializeArgValues(argsSpec, inputFieldOrder, argsDict) {
|
||||
const initIsEmpty = Object.keys(argsDict).length === 0;
|
||||
const argsValues = {};
|
||||
const argsDropdownOptions = {};
|
||||
uiSpec.order.flat().forEach((argkey) => {
|
||||
inputFieldOrder.flat().forEach((argkey) => {
|
||||
// When initializing with undefined values, assign defaults so that,
|
||||
// a) values are handled well by the html inputs and
|
||||
// b) the object exported to JSON on "Save" or "Execute" includes defaults.
|
||||
|
@ -129,12 +129,12 @@ class SetupTab extends React.Component {
|
|||
* not on every re-render.
|
||||
*/
|
||||
this._isMounted = true;
|
||||
const { argsInitValues, argsSpec, uiSpec } = this.props;
|
||||
const { argsInitValues, argsSpec, inputFieldOrder } = this.props;
|
||||
|
||||
const {
|
||||
argsValues,
|
||||
argsDropdownOptions,
|
||||
} = initializeArgValues(argsSpec, uiSpec, argsInitValues || {});
|
||||
} = initializeArgValues(argsSpec, inputFieldOrder, argsInitValues || {});
|
||||
|
||||
// map each arg to an empty object, to fill in later
|
||||
// here we use the argsSpec because it includes all args, even ones like
|
||||
|
@ -143,10 +143,10 @@ class SetupTab extends React.Component {
|
|||
acc[argkey] = {};
|
||||
return acc;
|
||||
}, {});
|
||||
// here we only use the keys in uiSpec.order because args that
|
||||
// here we only use the keys in inputFieldOrder because args that
|
||||
// aren't displayed in the form don't need an enabled/disabled state.
|
||||
// all args default to being enabled
|
||||
const argsEnabled = uiSpec.order.flat().reduce((acc, argkey) => {
|
||||
const argsEnabled = inputFieldOrder.flat().reduce((acc, argkey) => {
|
||||
acc[argkey] = true;
|
||||
return acc;
|
||||
}, {});
|
||||
|
@ -355,7 +355,6 @@ class SetupTab extends React.Component {
|
|||
* @returns {undefined}
|
||||
*/
|
||||
updateArgValues(key, value) {
|
||||
const { uiSpec } = this.props;
|
||||
const { argsValues } = this.state;
|
||||
argsValues[key].value = value;
|
||||
this.setState({
|
||||
|
@ -372,11 +371,11 @@ class SetupTab extends React.Component {
|
|||
* @param {object} argsDict - key: value pairs of InVEST arguments.
|
||||
*/
|
||||
batchUpdateArgs(argsDict) {
|
||||
const { argsSpec, uiSpec } = this.props;
|
||||
const { argsSpec, inputFieldOrder } = this.props;
|
||||
const {
|
||||
argsValues,
|
||||
argsDropdownOptions,
|
||||
} = initializeArgValues(argsSpec, uiSpec, argsDict);
|
||||
} = initializeArgValues(argsSpec, inputFieldOrder, argsDict);
|
||||
|
||||
this.setState({
|
||||
argsValues: argsValues,
|
||||
|
@ -539,10 +538,10 @@ class SetupTab extends React.Component {
|
|||
const {
|
||||
argsSpec,
|
||||
userguide,
|
||||
inputFieldOrder,
|
||||
sidebarSetupElementId,
|
||||
sidebarFooterElementId,
|
||||
executeClicked,
|
||||
uiSpec,
|
||||
modelID,
|
||||
} = this.props;
|
||||
|
||||
|
@ -594,7 +593,7 @@ class SetupTab extends React.Component {
|
|||
argsValidation={argsValidation}
|
||||
argsEnabled={argsEnabled}
|
||||
argsDropdownOptions={argsDropdownOptions}
|
||||
argsOrder={uiSpec.order}
|
||||
argsOrder={inputFieldOrder}
|
||||
userguide={userguide}
|
||||
updateArgValues={this.updateArgValues}
|
||||
updateArgTouched={this.updateArgTouched}
|
||||
|
@ -660,9 +659,7 @@ SetupTab.propTypes = {
|
|||
type: PropTypes.string,
|
||||
})
|
||||
).isRequired,
|
||||
uiSpec: PropTypes.shape({
|
||||
order: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)).isRequired,
|
||||
}).isRequired,
|
||||
inputFieldOrder: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)).isRequired,
|
||||
argsInitValues: PropTypes.objectOf(PropTypes.oneOfType(
|
||||
[PropTypes.string, PropTypes.bool, PropTypes.number])),
|
||||
investExecute: PropTypes.func.isRequired,
|
||||
|
|
|
@ -214,7 +214,7 @@ describe('Test building model UIs and forum links', () => {
|
|||
modelID={modelID}
|
||||
argsSpec={argsSpec.args}
|
||||
userguide={argsSpec.userguide}
|
||||
uiSpec={argsSpec.ui_spec}
|
||||
inputFieldOrder={argsSpec.input_field_order}
|
||||
argsInitValues={undefined}
|
||||
investExecute={() => {}}
|
||||
nWorkers="-1"
|
||||
|
|
|
@ -51,9 +51,7 @@ const SAMPLE_SPEC = {
|
|||
type: 'csv',
|
||||
},
|
||||
},
|
||||
ui_spec: {
|
||||
order: [['workspace_dir', 'carbon_pools_path']],
|
||||
},
|
||||
input_field_order: [['workspace_dir', 'carbon_pools_path']],
|
||||
};
|
||||
|
||||
describe('Various ways to open and close InVEST models', () => {
|
||||
|
|
|
@ -24,7 +24,8 @@ import {
|
|||
getInvestModelIDs,
|
||||
getSpec,
|
||||
fetchValidation,
|
||||
fetchArgsEnabled
|
||||
fetchArgsEnabled,
|
||||
getDynamicDropdowns
|
||||
} from '../../src/renderer/server_requests';
|
||||
import InvestJob from '../../src/renderer/InvestJob';
|
||||
|
||||
|
@ -47,9 +48,7 @@ describe('InVEST subprocess testing', () => {
|
|||
type: 'freestyle_string',
|
||||
},
|
||||
},
|
||||
ui_spec: {
|
||||
order: [['workspace_dir', 'results_suffix']],
|
||||
},
|
||||
input_field_order: [['workspace_dir', 'results_suffix']],
|
||||
model_name: 'EcoModel',
|
||||
pyname: 'natcap.invest.dot',
|
||||
userguide: 'foo.html',
|
||||
|
@ -111,6 +110,7 @@ describe('InVEST subprocess testing', () => {
|
|||
fetchArgsEnabled.mockResolvedValue({
|
||||
workspace_dir: true, results_suffix: true,
|
||||
});
|
||||
getDynamicDropdowns.mockResolvedValue({});
|
||||
getInvestModelIDs.mockResolvedValue(
|
||||
{ carbon: { model_title: modelTitle } }
|
||||
);
|
||||
|
|
|
@ -15,7 +15,8 @@ import {
|
|||
writeParametersToFile,
|
||||
fetchValidation,
|
||||
fetchDatastackFromFile,
|
||||
fetchArgsEnabled
|
||||
fetchArgsEnabled,
|
||||
getDynamicDropdowns
|
||||
} from '../../src/renderer/server_requests';
|
||||
import InvestJob from '../../src/renderer/InvestJob';
|
||||
import setupDialogs from '../../src/main/setupDialogs';
|
||||
|
@ -51,9 +52,7 @@ describe('Run status Alert renders with status from a recent run', () => {
|
|||
pyname: 'natcap.invest.foo',
|
||||
model_title: 'Foo Model',
|
||||
userguide: 'foo.html',
|
||||
ui_spec: {
|
||||
order: [['workspace']],
|
||||
},
|
||||
input_field_order: [['workspace']],
|
||||
args: {
|
||||
workspace: {
|
||||
name: 'Workspace',
|
||||
|
@ -67,6 +66,7 @@ describe('Run status Alert renders with status from a recent run', () => {
|
|||
getSpec.mockResolvedValue(spec);
|
||||
fetchValidation.mockResolvedValue([]);
|
||||
fetchArgsEnabled.mockResolvedValue({ workspace: true });
|
||||
getDynamicDropdowns.mockResolvedValue({});
|
||||
setupDialogs();
|
||||
});
|
||||
|
||||
|
@ -118,7 +118,7 @@ describe('Run status Alert renders with status from a recent run', () => {
|
|||
describe('Open Workspace button', () => {
|
||||
const spec = {
|
||||
args: {},
|
||||
ui_spec: { order: [] },
|
||||
input_field_order: [],
|
||||
};
|
||||
|
||||
const baseJob = {
|
||||
|
@ -129,6 +129,7 @@ describe('Open Workspace button', () => {
|
|||
beforeEach(() => {
|
||||
getSpec.mockResolvedValue(spec);
|
||||
fetchValidation.mockResolvedValue([]);
|
||||
getDynamicDropdowns.mockResolvedValue({});
|
||||
setupDialogs();
|
||||
});
|
||||
|
||||
|
@ -184,9 +185,7 @@ describe('Sidebar Buttons', () => {
|
|||
pyname: 'natcap.invest.foo',
|
||||
model_title: 'Foo Model',
|
||||
userguide: 'foo.html',
|
||||
ui_spec: {
|
||||
order: [['workspace', 'port']],
|
||||
},
|
||||
input_field_order: [['workspace', 'port']],
|
||||
args: {
|
||||
workspace: {
|
||||
name: 'Workspace',
|
||||
|
@ -204,6 +203,7 @@ describe('Sidebar Buttons', () => {
|
|||
getSpec.mockResolvedValue(spec);
|
||||
fetchValidation.mockResolvedValue([]);
|
||||
fetchArgsEnabled.mockResolvedValue({ workspace: true, port: true });
|
||||
getDynamicDropdowns.mockResolvedValue({});
|
||||
setupOpenExternalUrl();
|
||||
setupOpenLocalHtml();
|
||||
ipcRenderer.invoke.mockImplementation((channel) => {
|
||||
|
@ -547,9 +547,7 @@ describe('InVEST Run Button', () => {
|
|||
pyname: 'natcap.invest.bar',
|
||||
model_title: 'Bar Model',
|
||||
userguide: 'bar.html',
|
||||
ui_spec: {
|
||||
order: [['a', 'b', 'c']],
|
||||
},
|
||||
input_field_order: [['a', 'b', 'c']],
|
||||
args: {
|
||||
a: {
|
||||
name: 'abar',
|
||||
|
|
|
@ -34,9 +34,7 @@ describe('Add plugin modal', () => {
|
|||
type: 'raster',
|
||||
},
|
||||
},
|
||||
ui_spec: {
|
||||
order: [['workspace_dir', 'input_path']],
|
||||
},
|
||||
input_field_order: [['workspace_dir', 'input_path']],
|
||||
});
|
||||
|
||||
fetchArgsEnabled.mockResolvedValue({
|
||||
|
|
|
@ -31,9 +31,7 @@ const BASE_MODEL_SPEC = {
|
|||
about: 'this is about foo',
|
||||
},
|
||||
},
|
||||
ui_spec: {
|
||||
order: [['arg']]
|
||||
},
|
||||
input_field_order: [['arg']],
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -55,7 +53,7 @@ const BASE_ARGS_ENABLED = {}
|
|||
Object.keys(BASE_MODEL_SPEC.args).forEach((arg) => {
|
||||
BASE_ARGS_ENABLED[arg] = true;
|
||||
});
|
||||
const UI_SPEC = { order: [Object.keys(BASE_MODEL_SPEC.args)] };
|
||||
const INPUT_FIELD_ORDER = [Object.keys(BASE_MODEL_SPEC.args)];
|
||||
|
||||
/**
|
||||
* Render a SetupTab component given the necessary specs.
|
||||
|
@ -63,7 +61,7 @@ const UI_SPEC = { order: [Object.keys(BASE_MODEL_SPEC.args)] };
|
|||
* @param {object} baseSpec - an invest model spec
|
||||
* @returns {object} - containing the test utility functions returned by render
|
||||
*/
|
||||
function renderSetupFromSpec(baseSpec, uiSpec, initValues = undefined) {
|
||||
function renderSetupFromSpec(baseSpec, inputFieldOrder, initValues = undefined) {
|
||||
// some MODEL_SPEC boilerplate that is not under test,
|
||||
// but is required by PropType-checking
|
||||
const spec = { ...baseSpec };
|
||||
|
@ -75,7 +73,7 @@ function renderSetupFromSpec(baseSpec, uiSpec, initValues = undefined) {
|
|||
userguide={spec.userguide}
|
||||
modelID={spec.model_id}
|
||||
argsSpec={spec.args}
|
||||
uiSpec={uiSpec}
|
||||
inputFieldOrder={inputFieldOrder}
|
||||
argsInitValues={initValues}
|
||||
investExecute={() => {}}
|
||||
nWorkers="-1"
|
||||
|
@ -107,7 +105,7 @@ describe('Arguments form input types', () => {
|
|||
|
||||
const {
|
||||
findByLabelText, findByRole,
|
||||
} = renderSetupFromSpec(spec, UI_SPEC);
|
||||
} = renderSetupFromSpec(spec, INPUT_FIELD_ORDER);
|
||||
|
||||
const input = await findByLabelText(RegExp(`^${spec.args.arg.name}`));
|
||||
expect(input).toHaveAttribute('type', 'text');
|
||||
|
@ -122,21 +120,21 @@ describe('Arguments form input types', () => {
|
|||
['integer'],
|
||||
])('render a text input for a %s', async (type) => {
|
||||
const spec = baseArgsSpec(type);
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, UI_SPEC);
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, INPUT_FIELD_ORDER);
|
||||
const input = await findByLabelText((content) => content.startsWith(spec.args.arg.name));
|
||||
expect(input).toHaveAttribute('type', 'text');
|
||||
});
|
||||
|
||||
test('render a text input with unit label for a number', async () => {
|
||||
const spec = baseArgsSpec('number');
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, UI_SPEC);
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, INPUT_FIELD_ORDER);
|
||||
const input = await findByLabelText(`${spec.args.arg.name} (number) (${spec.args.arg.units})`);
|
||||
expect(input).toHaveAttribute('type', 'text');
|
||||
});
|
||||
|
||||
test('render an unchecked toggle switch for a boolean', async () => {
|
||||
const spec = baseArgsSpec('boolean');
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, UI_SPEC);
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, INPUT_FIELD_ORDER);
|
||||
const input = await findByLabelText(`${spec.args.arg.name}`);
|
||||
// for some reason, the type is still checkbox when it renders as a switch
|
||||
expect(input).toHaveAttribute('type', 'checkbox');
|
||||
|
@ -145,7 +143,7 @@ describe('Arguments form input types', () => {
|
|||
|
||||
test('render a toggle with a value', async () => {
|
||||
const spec = baseArgsSpec('boolean');
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, UI_SPEC, { arg: true });
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, INPUT_FIELD_ORDER, { arg: true });
|
||||
const input = await findByLabelText(`${spec.args.arg.name}`);
|
||||
// for some reason, the type is still checkbox when it renders as a switch
|
||||
expect(input).toBeChecked();
|
||||
|
@ -157,7 +155,7 @@ describe('Arguments form input types', () => {
|
|||
a: { display_name: 'Option A' },
|
||||
b: { display_name: 'Option B' },
|
||||
};
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, UI_SPEC);
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, INPUT_FIELD_ORDER);
|
||||
const input = await findByLabelText(`${spec.args.arg.name}`);
|
||||
expect(input).toHaveDisplayValue('Option A');
|
||||
expect(input).toHaveValue('a');
|
||||
|
@ -167,7 +165,7 @@ describe('Arguments form input types', () => {
|
|||
test('render a select input for an option_string list', async () => {
|
||||
const spec = baseArgsSpec('option_string');
|
||||
spec.args.arg.options = ['a', 'b'];
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, UI_SPEC);
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, INPUT_FIELD_ORDER);
|
||||
const input = await findByLabelText(`${spec.args.arg.name}`);
|
||||
expect(input).toHaveValue('a');
|
||||
expect(input).not.toHaveValue('b');
|
||||
|
@ -182,7 +180,7 @@ describe('Arguments form input types', () => {
|
|||
paramZ: missingValue, // paramZ is not in the ARGS_SPEC
|
||||
};
|
||||
|
||||
const { findByLabelText, queryByText } = renderSetupFromSpec(spec, UI_SPEC, initArgs);
|
||||
const { findByLabelText, queryByText } = renderSetupFromSpec(spec, INPUT_FIELD_ORDER, initArgs);
|
||||
const input = await findByLabelText(`${spec.args.arg.name} (number) (${spec.args.arg.units})`);
|
||||
await waitFor(() => expect(input).toHaveValue(displayedValue));
|
||||
expect(queryByText(missingValue)).toBeNull();
|
||||
|
@ -195,6 +193,7 @@ describe('Arguments form interactions', () => {
|
|||
[[Object.keys(BASE_MODEL_SPEC.args), VALIDATION_MESSAGE]]
|
||||
);
|
||||
fetchArgsEnabled.mockResolvedValue(BASE_ARGS_ENABLED);
|
||||
getDynamicDropdowns.mockResolvedValue({});
|
||||
setupOpenExternalUrl();
|
||||
});
|
||||
|
||||
|
@ -206,7 +205,7 @@ describe('Arguments form interactions', () => {
|
|||
const spec = baseArgsSpec('csv');
|
||||
const {
|
||||
findByRole, findByLabelText,
|
||||
} = renderSetupFromSpec(spec, UI_SPEC);
|
||||
} = renderSetupFromSpec(spec, INPUT_FIELD_ORDER);
|
||||
|
||||
const input = await findByLabelText((content) => content.startsWith(spec.args.arg.name));
|
||||
expect(input).toHaveAttribute('type', 'text');
|
||||
|
@ -235,7 +234,7 @@ describe('Arguments form interactions', () => {
|
|||
const spec = baseArgsSpec('csv');
|
||||
const {
|
||||
findByRole, findByLabelText,
|
||||
} = renderSetupFromSpec(spec, UI_SPEC);
|
||||
} = renderSetupFromSpec(spec, INPUT_FIELD_ORDER);
|
||||
|
||||
const filepath = 'grilled_cheese.csv';
|
||||
const mockDialogData = { filePaths: [filepath] };
|
||||
|
@ -253,7 +252,7 @@ describe('Arguments form interactions', () => {
|
|||
spec.args.arg.required = true;
|
||||
const {
|
||||
findByText, findByLabelText, queryByText,
|
||||
} = renderSetupFromSpec(spec, UI_SPEC);
|
||||
} = renderSetupFromSpec(spec, INPUT_FIELD_ORDER);
|
||||
|
||||
const input = await findByLabelText((content) => content.startsWith(spec.args.arg.name));
|
||||
|
||||
|
@ -282,7 +281,7 @@ describe('Arguments form interactions', () => {
|
|||
const spy = jest.spyOn(SetupTab.WrappedComponent.prototype, 'investValidate');
|
||||
const spec = baseArgsSpec('directory');
|
||||
spec.args.arg.required = true;
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, UI_SPEC);
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, INPUT_FIELD_ORDER);
|
||||
|
||||
const input = await findByLabelText((content) => content.startsWith(spec.args.arg.name));
|
||||
spy.mockClear(); // it was already called once on render
|
||||
|
@ -298,7 +297,7 @@ describe('Arguments form interactions', () => {
|
|||
const spy = jest.spyOn(SetupTab.WrappedComponent.prototype, 'investValidate');
|
||||
const spec = baseArgsSpec('directory');
|
||||
spec.args.arg.required = true;
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, UI_SPEC);
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, INPUT_FIELD_ORDER);
|
||||
|
||||
const input = await findByLabelText((content) => content.startsWith(spec.args.arg.name));
|
||||
spy.mockClear(); // it was already called once on render
|
||||
|
@ -316,7 +315,7 @@ describe('Arguments form interactions', () => {
|
|||
spec.args.arg.required = true;
|
||||
const {
|
||||
findByText, findByLabelText, queryByText,
|
||||
} = renderSetupFromSpec(spec, UI_SPEC);
|
||||
} = renderSetupFromSpec(spec, INPUT_FIELD_ORDER);
|
||||
|
||||
const input = await findByLabelText((content) => content.startsWith(spec.args.arg.name));
|
||||
expect(input).toHaveClass('is-invalid');
|
||||
|
@ -334,7 +333,7 @@ describe('Arguments form interactions', () => {
|
|||
const spec = baseArgsSpec('csv');
|
||||
spec.args.arg.required = false;
|
||||
fetchValidation.mockResolvedValue([]);
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, UI_SPEC);
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, INPUT_FIELD_ORDER);
|
||||
|
||||
const input = await findByLabelText((content) => content.includes('optional'));
|
||||
|
||||
|
@ -352,7 +351,7 @@ describe('Arguments form interactions', () => {
|
|||
const spy = jest.spyOn(ipcRenderer, 'send')
|
||||
.mockImplementation(() => Promise.resolve());
|
||||
const spec = baseArgsSpec('directory');
|
||||
const { findByText, findByRole } = renderSetupFromSpec(spec, UI_SPEC);
|
||||
const { findByText, findByRole } = renderSetupFromSpec(spec, INPUT_FIELD_ORDER);
|
||||
await userEvent.click(await findByRole('button', { name: /info about/ }));
|
||||
expect(await findByText(spec.args.arg.about)).toBeInTheDocument();
|
||||
const link = await findByRole('link', { name: /user guide/ });
|
||||
|
@ -396,21 +395,9 @@ describe('UI spec functionality', () => {
|
|||
};
|
||||
fetchArgsEnabled.mockResolvedValue({ arg1: false, arg2: true });
|
||||
|
||||
const uiSpec = {
|
||||
order: [Object.keys(spec.args)],
|
||||
enabledFunctions: {
|
||||
// enabled if arg1 is sufficient
|
||||
arg2: ((state) => state.argsEnabled.arg1 && !!state.argsValues.arg1.value),
|
||||
// enabled if arg1 and arg2 are sufficient
|
||||
arg3: ((state) => state.argsEnabled.arg1 && !!state.argsValues.arg1.value
|
||||
&& (state.argsEnabled.arg2 && !!state.argsValues.arg2.value)),
|
||||
// enabled if arg1 is sufficient and arg2 is not sufficient
|
||||
arg4: ((state) => state.argsEnabled.arg1 && !!state.argsValues.arg1.value
|
||||
&& !(state.argsEnabled.arg2 && !!state.argsValues.arg2.value)),
|
||||
},
|
||||
};
|
||||
const inputFieldOrder = [Object.keys(spec.args)];
|
||||
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, uiSpec);
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, inputFieldOrder);
|
||||
const arg1 = await findByLabelText((content) => content.startsWith(spec.args.arg1.name));
|
||||
const arg2 = await findByLabelText((content) => content.startsWith(spec.args.arg2.name));
|
||||
const arg3 = await findByLabelText((content) => content.startsWith(spec.args.arg3.name));
|
||||
|
@ -435,18 +422,15 @@ describe('UI spec functionality', () => {
|
|||
name: 'bfoo',
|
||||
type: 'option_string',
|
||||
options: {},
|
||||
dropdown_function: 'function to retrieve arg1 column names',
|
||||
},
|
||||
},
|
||||
};
|
||||
const uiSpec = {
|
||||
order: [Object.keys(spec.args)],
|
||||
dropdown_functions: {
|
||||
arg2: 'function to retrieve arg1 column names',
|
||||
},
|
||||
};
|
||||
const inputFieldOrder = [Object.keys(spec.args)];
|
||||
|
||||
const {
|
||||
findByLabelText, findByText, queryByText,
|
||||
} = renderSetupFromSpec(spec, uiSpec);
|
||||
} = renderSetupFromSpec(spec, inputFieldOrder);
|
||||
const arg1 = await findByLabelText((content) => content.startsWith(spec.args.arg1.name));
|
||||
let option = await queryByText('Field1');
|
||||
expect(option).toBeNull();
|
||||
|
@ -487,12 +471,10 @@ describe('UI spec functionality', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const uiSpec = {
|
||||
// intentionally leaving out arg6, it should not be in the setup form
|
||||
order: [['arg4'], ['arg3', 'arg2'], ['arg1'], ['arg5']],
|
||||
};
|
||||
// intentionally leaving out arg6, it should not be in the setup form
|
||||
const inputFieldOrder = [['arg4'], ['arg3', 'arg2'], ['arg1'], ['arg5']];
|
||||
|
||||
const { findByTestId, queryByText } = renderSetupFromSpec(spec, uiSpec);
|
||||
const { findByTestId, queryByText } = renderSetupFromSpec(spec, inputFieldOrder);
|
||||
const form = await findByTestId('setup-form');
|
||||
|
||||
await waitFor(() => {
|
||||
|
@ -534,7 +516,7 @@ describe('Misc form validation stuff', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const uiSpec = { order: [Object.keys(spec.args)] };
|
||||
const inputFieldOrder = [Object.keys(spec.args)];
|
||||
|
||||
// Mocking to return the payload so we can assert we always send
|
||||
// correct payload to this endpoint.
|
||||
|
@ -543,7 +525,7 @@ describe('Misc form validation stuff', () => {
|
|||
);
|
||||
fetchArgsEnabled.mockResolvedValue({ a: true, b: true, c: true });
|
||||
|
||||
renderSetupFromSpec(spec, uiSpec);
|
||||
renderSetupFromSpec(spec, inputFieldOrder);
|
||||
await waitFor(() => {
|
||||
const expectedKeys = ['model_id', 'args'];
|
||||
const payload = fetchValidation.mock.results[0].value;
|
||||
|
@ -566,7 +548,7 @@ describe('Misc form validation stuff', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const uiSpec = { order: [Object.keys(spec.args)] };
|
||||
const inputFieldOrder =[Object.keys(spec.args)];
|
||||
const vectorValue = './vector.shp';
|
||||
const expectedVal1 = '-84.9';
|
||||
const vectorBox = `[${expectedVal1}, 19.1, -69.1, 29.5]`;
|
||||
|
@ -580,7 +562,7 @@ describe('Misc form validation stuff', () => {
|
|||
|
||||
fetchValidation.mockResolvedValue([[Object.keys(spec.args), message]]);
|
||||
fetchArgsEnabled.mockResolvedValue({ vector: true, raster: true });
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, uiSpec);
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, inputFieldOrder);
|
||||
const vectorInput = await findByLabelText((content) => content.startsWith(spec.args.vector.name));
|
||||
const rasterInput = await findByLabelText(RegExp(`^${spec.args.raster.name}`));
|
||||
await userEvent.type(vectorInput, vectorValue);
|
||||
|
@ -621,7 +603,7 @@ describe('Form drag-and-drop', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const uiSpec = { order: [Object.keys(spec.args)] };
|
||||
const inputFieldOrder = [Object.keys(spec.args)];
|
||||
fetchValidation.mockResolvedValue(
|
||||
[[Object.keys(spec.args), VALIDATION_MESSAGE]]
|
||||
);
|
||||
|
@ -638,7 +620,7 @@ describe('Form drag-and-drop', () => {
|
|||
|
||||
const {
|
||||
findByLabelText, findByTestId,
|
||||
} = renderSetupFromSpec(spec, uiSpec);
|
||||
} = renderSetupFromSpec(spec, inputFieldOrder);
|
||||
const setupForm = await findByTestId('setup-form');
|
||||
|
||||
// This should work but doesn't due to lack of dataTransfer object in jsdom:
|
||||
|
@ -684,7 +666,7 @@ describe('Form drag-and-drop', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const uiSpec = { order: [Object.keys(spec.args)] };
|
||||
const inputFieldOrder = [Object.keys(spec.args)];
|
||||
fetchValidation.mockResolvedValue(
|
||||
[[Object.keys(spec.args), VALIDATION_MESSAGE]]
|
||||
);
|
||||
|
@ -701,7 +683,7 @@ describe('Form drag-and-drop', () => {
|
|||
|
||||
const {
|
||||
findByLabelText, findByTestId,
|
||||
} = renderSetupFromSpec(spec, uiSpec);
|
||||
} = renderSetupFromSpec(spec, inputFieldOrder);
|
||||
const setupForm = await findByTestId('setup-form');
|
||||
|
||||
const fileDragEvent = createEvent.dragEnter(setupForm);
|
||||
|
@ -747,13 +729,13 @@ describe('Form drag-and-drop', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const uiSpec = { order: [Object.keys(spec.args)] };
|
||||
const inputFieldOrder = [Object.keys(spec.args)];
|
||||
fetchValidation.mockResolvedValue(
|
||||
[[Object.keys(spec.args), VALIDATION_MESSAGE]]
|
||||
);
|
||||
fetchArgsEnabled.mockResolvedValue({ arg1: true, arg2: true });
|
||||
|
||||
const { findByTestId } = renderSetupFromSpec(spec, uiSpec);
|
||||
const { findByTestId } = renderSetupFromSpec(spec, inputFieldOrder);
|
||||
const setupForm = await findByTestId('setup-form');
|
||||
|
||||
const fileDragEnterEvent = createEvent.dragEnter(setupForm);
|
||||
|
@ -792,7 +774,7 @@ describe('Form drag-and-drop', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const uiSpec = { order: [Object.keys(spec.args)] };
|
||||
const inputFieldOrder = [Object.keys(spec.args)];
|
||||
fetchValidation.mockResolvedValue(
|
||||
[[Object.keys(spec.args), VALIDATION_MESSAGE]]
|
||||
);
|
||||
|
@ -800,7 +782,7 @@ describe('Form drag-and-drop', () => {
|
|||
|
||||
const {
|
||||
findByLabelText, findByTestId,
|
||||
} = renderSetupFromSpec(spec, uiSpec);
|
||||
} = renderSetupFromSpec(spec, inputFieldOrder);
|
||||
const setupForm = await findByTestId('setup-form');
|
||||
const setupInput = await findByLabelText((content) => content.startsWith(spec.args.arg1.name));
|
||||
|
||||
|
@ -846,13 +828,13 @@ describe('Form drag-and-drop', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const uiSpec = { order: [Object.keys(spec.args)] };
|
||||
const inputFieldOrder = [Object.keys(spec.args)];
|
||||
fetchValidation.mockResolvedValue(
|
||||
[[Object.keys(spec.args), VALIDATION_MESSAGE]]
|
||||
);
|
||||
fetchArgsEnabled.mockResolvedValue({ arg1: true, arg2: true });
|
||||
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, uiSpec);
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, inputFieldOrder);
|
||||
const setupInput = await findByLabelText((content) => content.startsWith(spec.args.arg1.name));
|
||||
|
||||
const fileDragEnterEvent = createEvent.dragEnter(setupInput);
|
||||
|
@ -891,22 +873,18 @@ describe('Form drag-and-drop', () => {
|
|||
arg2: {
|
||||
name: 'AOI',
|
||||
type: 'vector',
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
const uiSpec = {
|
||||
order: [Object.keys(spec.args)],
|
||||
enabledFunctions: {
|
||||
arg2: (() => false), // make this arg always disabled
|
||||
},
|
||||
};
|
||||
const inputFieldOrder = [Object.keys(spec.args)];
|
||||
|
||||
fetchValidation.mockResolvedValue(
|
||||
[[Object.keys(spec.args), VALIDATION_MESSAGE]]
|
||||
);
|
||||
fetchArgsEnabled.mockResolvedValue({ arg1: true, arg2: false });
|
||||
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, uiSpec);
|
||||
const { findByLabelText } = renderSetupFromSpec(spec, inputFieldOrder);
|
||||
const setupInput = await findByLabelText((content) => content.startsWith(spec.args.arg2.name));
|
||||
|
||||
const fileDragEnterEvent = createEvent.dragEnter(setupInput);
|
||||
|
|
Loading…
Reference in New Issue