mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2024-11-25 18:20:20 -06:00
Port Cast node to react. Fixes #6566.
This commit is contained in:
parent
d846ae64ea
commit
c94fc73c11
@ -6,12 +6,16 @@
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import { getNodeAjaxOptions } from '../../../../../../static/js/node_ajax';
|
||||
import CastSchema from './cast.ui';
|
||||
import getApiInstance from '../../../../../../../static/js/api_instance';
|
||||
|
||||
|
||||
define('pgadmin.node.cast', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'sources/pgadmin', 'pgadmin.browser',
|
||||
'pgadmin.alertifyjs', 'pgadmin.backform', 'pgadmin.browser.collection',
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser, alertify, Backform) {
|
||||
], function(gettext, url_for, $, _, pgAdmin, pgBrowser) {
|
||||
// Extend the collection class for cast
|
||||
if (!pgBrowser.Nodes['coll-cast']) {
|
||||
pgAdmin.Browser.Nodes['coll-cast'] =
|
||||
@ -68,16 +72,6 @@ define('pgadmin.node.cast', [
|
||||
// Define the backform model for cast node
|
||||
model: pgAdmin.Browser.Node.Model.extend({
|
||||
idAttribute: 'oid',
|
||||
defaults: {
|
||||
name: undefined, // Name of the cast
|
||||
encoding: 'UTF8',
|
||||
srctyp: undefined, // Source type
|
||||
trgtyp: undefined, // Target type
|
||||
proname: undefined, // Function
|
||||
castcontext: undefined, // Context (IMPLICIT/EXPLICIT/ASSIGNMENT)
|
||||
syscast: undefined, // Is this cast is system object? Yes/No
|
||||
description: undefined, // Comment on the cast
|
||||
},
|
||||
|
||||
// Define the schema for cast
|
||||
schema: [{
|
||||
@ -86,224 +80,47 @@ define('pgadmin.node.cast', [
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
editable: false, type: 'text', mode: ['properties'],
|
||||
},{
|
||||
id: 'srctyp', label: gettext('Source type'), url: 'get_type',
|
||||
type: 'text', group: gettext('Definition'), readonly: function(m) {
|
||||
return !m.isNew();
|
||||
}, mode: ['create'],
|
||||
|
||||
transform: function(rows) {
|
||||
_.each(rows, function(r) {
|
||||
r['image'] = 'icon-cast';
|
||||
});
|
||||
return rows;
|
||||
},
|
||||
|
||||
/*
|
||||
* Control is extended to create cast name from source type and destination type
|
||||
* once their values are changed
|
||||
*/
|
||||
control: Backform.NodeAjaxOptionsControl.extend({
|
||||
|
||||
onChange: function() {
|
||||
Backform.NodeAjaxOptionsControl.prototype.onChange.apply(
|
||||
this, arguments
|
||||
);
|
||||
|
||||
/*
|
||||
* On source type change, check if both source type and
|
||||
* target type are set, if yes then fetch values from both
|
||||
* controls and generate cast name
|
||||
*/
|
||||
var srctype = this.model.get('srctyp');
|
||||
var trgtype = this.model.get('trgtyp');
|
||||
if(srctype != undefined && srctype != '' &&
|
||||
trgtype != undefined && trgtype != '')
|
||||
this.model.set('name', srctype+'->'+trgtype);
|
||||
else
|
||||
this.model.unset('name');
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
/*
|
||||
* Text control for viewing source type in properties and
|
||||
* edit mode only
|
||||
*/
|
||||
{
|
||||
id: 'srctyp', label: gettext('Source type'), type: 'text',
|
||||
group: gettext('Definition'), readonly: true, mode:['properties','edit'],
|
||||
},{
|
||||
id: 'trgtyp', label: gettext('Target type'), url: 'get_type',
|
||||
type: 'text', group: gettext('Definition'), readonly: function(m) {
|
||||
return !m.isNew();
|
||||
}, mode: ['create'],
|
||||
transform: function(rows) {
|
||||
_.each(rows, function(r) {
|
||||
r['image'] = 'icon-cast';
|
||||
});
|
||||
return rows;
|
||||
},
|
||||
|
||||
/*
|
||||
* Control is extended to create cast name from source type and destination type
|
||||
* once their values are changed
|
||||
*/
|
||||
control: Backform.NodeAjaxOptionsControl.extend({
|
||||
|
||||
onChange: function() {
|
||||
Backform.NodeAjaxOptionsControl.prototype.onChange.apply(
|
||||
this, arguments
|
||||
);
|
||||
|
||||
/*
|
||||
* on target type change, check if both source type and
|
||||
* target type are set, if yes then fetch values from both
|
||||
* controls and generate cast name
|
||||
*/
|
||||
var srcType = this.model.get('srctyp');
|
||||
var trgtype = this.model.get('trgtyp');
|
||||
if(srcType != undefined && srcType != '' &&
|
||||
trgtype != undefined && trgtype != '')
|
||||
this.model.set('name', srcType+'->'+trgtype);
|
||||
else
|
||||
this.model.unset('name');
|
||||
},
|
||||
}),
|
||||
},
|
||||
/*
|
||||
* Text control for viewing target type in properties and
|
||||
* edit mode only
|
||||
*/
|
||||
{
|
||||
id: 'trgtyp', label: gettext('Target type'), type: 'text',
|
||||
group: gettext('Definition'), readonly: true, mode:['properties','edit'],
|
||||
},
|
||||
|
||||
/*
|
||||
* Proname field is dependent on source type and target type.
|
||||
* On source and target type changed event,
|
||||
* associated functions will be fetch using ajax call
|
||||
*/
|
||||
{
|
||||
id: 'proname', label: gettext('Function'), deps:['srctyp', 'trgtyp'],
|
||||
type: 'text', readonly: function(m) { return !m.isNew(); },
|
||||
group: gettext('Definition'), mode: ['create'],
|
||||
control: 'node-ajax-options',
|
||||
options: function(control) {
|
||||
var srcTyp = control.model.get('srctyp');
|
||||
var trgtyp = control.model.get('trgtyp');
|
||||
var res = [];
|
||||
|
||||
if(srcTyp != undefined && srcTyp != '' &&
|
||||
trgtyp != undefined && trgtyp != '')
|
||||
{
|
||||
var node = control.field.get('schema_node'),
|
||||
_url = node.generate_url.apply(
|
||||
node, [
|
||||
null, 'get_functions', control.field.get('node_data'), false,
|
||||
control.field.get('node_info'),
|
||||
]);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
timeout: 30000,
|
||||
url: _url,
|
||||
cache: false,
|
||||
async: false,
|
||||
data: {'srctyp' : srcTyp, 'trgtyp' : trgtyp},
|
||||
})
|
||||
// On success return function list from server
|
||||
.done(function(result) {
|
||||
res = result.data;
|
||||
return res;
|
||||
})
|
||||
// On failure show error appropriate error message to user
|
||||
.fail(function(xhr, status, error) {
|
||||
alertify.pgRespErrorNotify(xhr, error);
|
||||
});
|
||||
}
|
||||
return res;
|
||||
},
|
||||
},
|
||||
/*
|
||||
* Text type control for viewing function name in properties and
|
||||
* edit mode only
|
||||
*/
|
||||
{
|
||||
id: 'proname', label: gettext('Function'), type: 'text',
|
||||
group: gettext('Definition'), readonly: true, mode:['properties','edit'],
|
||||
},{
|
||||
id: 'castcontext', label: gettext('Context'),
|
||||
options:{'onText':'IMPLICIT','offText':'EXPLICIT', width: '90'},
|
||||
editable: false, type: 'string', group: gettext('Definition'),
|
||||
mode:['create'],
|
||||
control: Backform.SwitchControl.extend({
|
||||
getValueFromDOM: function() {
|
||||
return this.$input.prop('checked') ? 'IMPLICIT' : 'EXPLICIT';
|
||||
},
|
||||
}),
|
||||
},
|
||||
/*
|
||||
* Text control for viewing context in properties and
|
||||
* edit mode
|
||||
*/
|
||||
{
|
||||
id: 'castcontext', label: gettext('Context'), readonly: true,
|
||||
options:[{
|
||||
label: 'IMPLICIT', value: 'IMPLICIT',
|
||||
},{
|
||||
label: 'EXPLICIT', value: 'EXPLICIT',
|
||||
},{
|
||||
label: 'ASSIGNMENT', value: 'ASSIGNMENT',
|
||||
}], editable: false, type: 'select2', group: gettext('Definition'),
|
||||
mode:['properties', 'edit'],
|
||||
},{
|
||||
id: 'syscast', label: gettext('System cast?'),
|
||||
cell: 'switch', type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'),
|
||||
type: 'multiline', cellHeaderClasses: 'width_percent_50',
|
||||
},
|
||||
],
|
||||
|
||||
/*
|
||||
* Triggers control specific error messages for source type and
|
||||
* target type if any one of them is not selected while creating
|
||||
* new cast
|
||||
*/
|
||||
validate: function() {
|
||||
|
||||
var srctype = this.get('srctyp'),
|
||||
trgtype = this.get('trgtyp'),
|
||||
msg;
|
||||
|
||||
// validate source type control
|
||||
if (
|
||||
_.isUndefined(srctype) || _.isNull(srctype) ||
|
||||
String(srctype).replace(/^\s+|\s+$/g, '') == ''
|
||||
) {
|
||||
msg = gettext('Source type must be selected.');
|
||||
this.errorModel.set('srctyp', msg);
|
||||
return msg;
|
||||
} else {
|
||||
this.errorModel.unset('srctyp');
|
||||
}
|
||||
|
||||
// validate target type control
|
||||
if (
|
||||
_.isUndefined(trgtype) || _.isNull(trgtype) ||
|
||||
String(trgtype).replace(/^\s+|\s+$/g, '') == ''
|
||||
) {
|
||||
msg = gettext('Target type must be selected.');
|
||||
this.errorModel.set('trgtyp', msg);
|
||||
return msg;
|
||||
} else {
|
||||
this.errorModel.unset('trgtyp');
|
||||
}
|
||||
this.trigger('on-status-clear');
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
|
||||
getSchema: function(treeNodeInfo, itemNodeData){
|
||||
let schema = new CastSchema({
|
||||
getTypeOptions: ()=>getNodeAjaxOptions('get_type', this, treeNodeInfo, itemNodeData),
|
||||
getFuncOptions: (srcTyp, trgtyp) =>
|
||||
{
|
||||
return new Promise((resolve, reject)=>{
|
||||
const api = getApiInstance();
|
||||
|
||||
var _url = pgBrowser.Nodes['cast'].generate_url.apply(
|
||||
pgBrowser.Nodes['cast'], [
|
||||
null, 'get_functions', itemNodeData, false,
|
||||
treeNodeInfo,
|
||||
]);
|
||||
var data = {'srctyp' : srcTyp, 'trgtyp' : trgtyp};
|
||||
|
||||
if(srcTyp != undefined && srcTyp != '' &&
|
||||
trgtyp != undefined && trgtyp != ''){
|
||||
|
||||
api.post(_url, data)
|
||||
.then(res=>{
|
||||
data = res.data.data;
|
||||
resolve(data);
|
||||
})
|
||||
.catch((err)=>{
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
return schema;
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,193 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import gettext from 'sources/gettext';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
|
||||
|
||||
|
||||
export default class CastSchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, initValues) {
|
||||
super({
|
||||
name: undefined, // Name of the cast
|
||||
encoding: 'UTF8',
|
||||
srctyp: undefined, // Source type
|
||||
trgtyp: undefined, // Target type
|
||||
proname: undefined, // Function
|
||||
castcontext: true, // Context (IMPLICIT/EXPLICIT/ASSIGNMENT)
|
||||
syscast: undefined, // Is this cast is system object? Yes/No
|
||||
description: undefined, // Comment on the cast
|
||||
...initValues,
|
||||
});
|
||||
this.fieldOptions=fieldOptions;
|
||||
|
||||
}
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
let obj = this;
|
||||
return [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
editable: false, type: 'text', readonly: true, cellHeaderClasses: 'width_percent_50',
|
||||
},{
|
||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
||||
editable: false, type: 'text', mode: ['properties'],
|
||||
},{
|
||||
id: 'srctyp', label: gettext('Source type'),
|
||||
type: 'select', group: gettext('Definition'), readonly: function(state) {
|
||||
return !obj.isNew(state);
|
||||
}, mode: ['create'],
|
||||
options:this.fieldOptions.getTypeOptions,
|
||||
/*
|
||||
* Control is extended to create cast name from source type and destination type
|
||||
* once their values are changed
|
||||
*/
|
||||
depChange: (state)=>{
|
||||
/*
|
||||
* On source type change, check if both source type and
|
||||
* target type are set, if yes then fetch values from both
|
||||
* controls and generate cast name
|
||||
*/
|
||||
var srctype = state.srctyp;
|
||||
var trgtype = state.trgtyp;
|
||||
if(srctype != undefined && srctype != '' &&
|
||||
trgtype != undefined && trgtype != '')
|
||||
return state.name = srctype+'->'+trgtype;
|
||||
else
|
||||
return state.name = '';
|
||||
},
|
||||
},
|
||||
|
||||
/*
|
||||
* Text control for viewing source type in properties and
|
||||
* edit mode only
|
||||
*/
|
||||
{
|
||||
id: 'srctyp', label: gettext('Source type'), type: 'text',
|
||||
group: gettext('Definition'), readonly: true, mode:['properties','edit'],
|
||||
},{
|
||||
id: 'trgtyp', label: gettext('Target type'),
|
||||
type: 'select', group: gettext('Definition'), readonly: function(state) {
|
||||
return !obj.isNew(state);
|
||||
}, mode: ['create'],
|
||||
options:this.fieldOptions.getTypeOptions,
|
||||
|
||||
depChange: (state)=>{
|
||||
/*
|
||||
* On source type change, check if both source type and
|
||||
* target type are set, if yes then fetch values from both
|
||||
* controls and generate cast name
|
||||
*/
|
||||
var srctype = state.srctyp;
|
||||
var trgtype = state.trgtyp;
|
||||
if(srctype != undefined && srctype != '' &&
|
||||
trgtype != undefined && trgtype != '')
|
||||
return state.name = srctype+'->'+trgtype;
|
||||
else
|
||||
return state.name = '';
|
||||
},
|
||||
},
|
||||
/*
|
||||
* Text control for viewing target type in properties and
|
||||
* edit mode only
|
||||
*/
|
||||
{
|
||||
id: 'trgtyp', label: gettext('Target type'), type: 'text',
|
||||
group: gettext('Definition'), readonly: true, mode:['properties','edit'],
|
||||
},
|
||||
|
||||
/*
|
||||
* Proname field is dependent on source type and target type.
|
||||
* On source and target type changed event,
|
||||
* associated functions will be fetch using ajax call
|
||||
*/
|
||||
{
|
||||
id: 'proname', label: gettext('Function'), deps:['srctyp', 'trgtyp'],
|
||||
readonly: function(state) { return !obj.isNew(state); },
|
||||
group: gettext('Definition'), mode: ['create'],
|
||||
first_empty: true,
|
||||
type: (state)=>{
|
||||
|
||||
let fetchOptionsBasis = state.srctyp + state.trgtyp;
|
||||
return {
|
||||
type: 'select',
|
||||
options: ()=>obj.fieldOptions.getFuncOptions(state.srctyp, state.trgtyp),
|
||||
optionsReloadBasis: fetchOptionsBasis,
|
||||
};
|
||||
},
|
||||
},
|
||||
/*
|
||||
* Text type control for viewing function name in properties and
|
||||
* edit mode only
|
||||
*/
|
||||
{
|
||||
id: 'proname', label: gettext('Function'), type: 'text',
|
||||
group: gettext('Definition'), readonly: true, mode:['properties','edit'],
|
||||
},
|
||||
{
|
||||
id: 'castcontext', label: gettext('Context'),type: 'toggle',
|
||||
options: [
|
||||
{'label': gettext('IMPLICIT'), value: true},
|
||||
{'label': gettext('EXPLICIT'), value: false},
|
||||
],
|
||||
group: gettext('Definition'),
|
||||
mode:['create'],
|
||||
},
|
||||
/*
|
||||
* Text control for viewing context in properties and
|
||||
* edit mode
|
||||
*/
|
||||
{
|
||||
id: 'castcontext', label: gettext('Context'), readonly: true,
|
||||
options:[{
|
||||
label: 'IMPLICIT', value: 'IMPLICIT',
|
||||
},{
|
||||
label: 'EXPLICIT', value: 'EXPLICIT',
|
||||
},{
|
||||
label: 'ASSIGNMENT', value: 'ASSIGNMENT',
|
||||
}], type: 'select', group: gettext('Definition'),
|
||||
mode:['properties', 'edit'],
|
||||
controlProps: {
|
||||
editable: false,
|
||||
},
|
||||
},{
|
||||
id: 'syscast', label: gettext('System cast?'),
|
||||
cell: 'switch', type: 'switch', mode: ['properties'],
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'),
|
||||
type: 'multiline', cellHeaderClasses: 'width_percent_50',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
validate(state, setError) {
|
||||
let errmsg = null;
|
||||
if (isEmptyString(state.srctyp)) {
|
||||
errmsg = gettext('Source type must be selected.');
|
||||
setError('srctyp', errmsg);
|
||||
return true;
|
||||
} else {
|
||||
errmsg = null;
|
||||
setError('srctyp', errmsg);
|
||||
}
|
||||
|
||||
if (isEmptyString(state.trgtyp)) {
|
||||
errmsg = gettext('Target type must be selected.');
|
||||
setError('trgtyp', errmsg);
|
||||
return true;
|
||||
} else {
|
||||
errmsg = null;
|
||||
setError('trgtyp', errmsg);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,9 @@ CREATE CAST ({{ conn|qtTypeIdent(data.srctyp) }} AS {{ conn|qtTypeIdent(data.trg
|
||||
{% if data.proname and data.proname != 'binary compatible'%}
|
||||
WITH FUNCTION {{data.proname}}{% else %}
|
||||
WITHOUT FUNCTION{% endif %}
|
||||
{% if data.castcontext and data.castcontext != 'EXPLICIT' %}
|
||||
{% if data.castcontext and data.castcontext != 'false' %}
|
||||
|
||||
AS {{data.castcontext}}{% endif %};
|
||||
AS {{'IMPLICIT'}}{% endif %};
|
||||
{# Description for CAST #}
|
||||
{% if data.description %}
|
||||
COMMENT ON CAST ({{ conn|qtTypeIdent(data.srctyp) }} AS {{ conn|qtTypeIdent(data.trgtyp) }})
|
||||
|
112
web/regression/javascript/schema_ui_files/cast.ui.spec.js
Normal file
112
web/regression/javascript/schema_ui_files/cast.ui.spec.js
Normal file
@ -0,0 +1,112 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
import React from 'react';
|
||||
import '../helper/enzyme.helper';
|
||||
import { createMount } from '@material-ui/core/test-utils';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import {messages} from '../fake_messages';
|
||||
import SchemaView from '../../../pgadmin/static/js/SchemaView';
|
||||
import CastSchema from '../../../pgadmin/browser/server_groups/servers/databases/casts/static/js/cast.ui';
|
||||
|
||||
|
||||
describe('CastSchema', ()=>{
|
||||
let mount;
|
||||
let schemaObj = new CastSchema(
|
||||
{
|
||||
getTypeOptions: ()=>[],
|
||||
getFuncOptions: ()=>[],
|
||||
},
|
||||
);
|
||||
let getInitData = ()=>Promise.resolve({});
|
||||
|
||||
/* Use createMount so that material ui components gets the required context */
|
||||
/* https://material-ui.com/guides/testing/#api */
|
||||
beforeAll(()=>{
|
||||
mount = createMount();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mount.cleanUp();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
jasmineEnzyme();
|
||||
/* messages used by validators */
|
||||
pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
|
||||
pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
|
||||
});
|
||||
|
||||
it('create', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('edit', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='dialog'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
}}
|
||||
onSave={()=>{}}
|
||||
onClose={()=>{}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
onDataChange={()=>{}}
|
||||
confirmOnCloseReset={false}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={false}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('properties', ()=>{
|
||||
mount(<SchemaView
|
||||
formType='tab'
|
||||
schema={schemaObj}
|
||||
getInitData={getInitData}
|
||||
viewHelperProps={{
|
||||
mode: 'properties',
|
||||
}}
|
||||
onHelp={()=>{}}
|
||||
onEdit={()=>{}}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('validate', ()=>{
|
||||
let state = {};
|
||||
let setError = jasmine.createSpy('setError');
|
||||
|
||||
state.srctyp = null;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('srctyp', 'Source type must be selected.');
|
||||
|
||||
state.srctyp = 'bigint';
|
||||
state.trgtyp = null;
|
||||
schemaObj.validate(state, setError);
|
||||
expect(setError).toHaveBeenCalledWith('trgtyp', 'Target type must be selected.');
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user