
/* /web/static/tests/_framework/async_step.js */
odoo.define('@web/../tests/_framework/async_step',['@odoo/hoot'],function(require){'use strict';let __exports={};const{expect}=require("@odoo/hoot");const asyncStep=__exports.asyncStep=expect.step;const waitForSteps=__exports.waitForSteps=expect.waitForSteps;return __exports;});;

/* /web/static/tests/_framework/component_test_helpers.js */
odoo.define('@web/../tests/_framework/component_test_helpers',['@odoo/hoot','@odoo/owl','@web/core/l10n/translation','@web/core/main_components_container','@web/core/popover/popover','@web/core/templates','@web/core/utils/arrays','@web/core/utils/patch','@web/../tests/_framework/env_test_helpers'],function(require){'use strict';let __exports={};const{after,destroy,getFixture,queryFirst,queryOne}=require("@odoo/hoot");const{App,Component,xml}=require("@odoo/owl");const{_t}=require("@web/core/l10n/translation");const{MainComponentsContainer}=require("@web/core/main_components_container");const{getPopoverForTarget}=require("@web/core/popover/popover");const{getTemplate:getTemplateFn}=require("@web/core/templates");const{isIterable}=require("@web/core/utils/arrays");const{patch}=require("@web/core/utils/patch");const{getMockEnv,makeMockEnv}=require("@web/../tests/_framework/env_test_helpers");const mountComponentWithCleanup=(ComponentClass,targetEl,config)=>{const app=new App(ComponentClass,config);after(()=>destroy(app));return app.mount(targetEl);};patch(MainComponentsContainer.prototype,{setup(){super.setup();hasMainComponent=true;after(()=>(hasMainComponent=false));},});let hasMainComponent=false;__exports.findComponent=findComponent;function findComponent(parent,predicate){const rootNode=parent instanceof App?parent.root:parent.__owl__;const queue=[rootNode,...Object.values(rootNode.children)];while(queue.length){const{children,component}=queue.pop();if(predicate(component)){return component;}
queue.unshift(...Object.values(children));}
return null;}
__exports.getDropdownMenu=getDropdownMenu;function getDropdownMenu(togglerSelector){let el=queryFirst(togglerSelector);if(el&&!el.classList.contains("o-dropdown")){el=el.querySelector(".o-dropdown");}
if(!el){throw new Error(`getDropdownMenu: Could not find element "${togglerSelector}".`);}
return getPopoverForTarget(el);}
__exports.mountWithCleanup=mountWithCleanup;async function mountWithCleanup(ComponentClass,options){const{componentEnv,containerEnv,customDirectives,env,fixtureClassName="o_web_client",getTemplate=getTemplateFn,globalValues,noMainContainer,props,target,templates,translatableAttributes,translateFn=_t,}=options||{};const commonConfig={customDirectives,getTemplate,globalValues,templates,translatableAttributes,translateFn,dev:false,test:true,warnIfNoStaticProps:true,};const fixture=getFixture();const targetEl=target?queryOne(target):fixture;if(fixtureClassName){const list=isIterable(fixtureClassName)?fixtureClassName:[fixtureClassName];fixture.classList.add(...list);}
if(typeof ComponentClass==="string"){ComponentClass=class extends Component{static name="anonymous component";static props={};static template=xml`${ComponentClass}`;};}
const commonEnv=env||getMockEnv()||(await makeMockEnv());const componentConfig={...commonConfig,env:Object.assign(Object.create(commonEnv),componentEnv),name:`TEST: ${ComponentClass.name}`,props,};const component=await mountComponentWithCleanup(ComponentClass,targetEl,componentConfig);if(!noMainContainer&&!hasMainComponent){const containerConfig={...commonConfig,env:Object.assign(Object.create(commonEnv),containerEnv),name:`TEST: ${ComponentClass.name} (main container)`,props:{},};await mountComponentWithCleanup(MainComponentsContainer,targetEl,containerConfig);}
return component;}
return __exports;});;

/* /web/static/tests/_framework/dom_test_helpers.js */
odoo.define('@web/../tests/_framework/dom_test_helpers',['@odoo/hoot','@web/core/browser/feature_detection'],function(require){'use strict';let __exports={};const{advanceFrame,advanceTime,after,afterEach,animationFrame,check,clear,click,dblclick,drag,edit,fill,getActiveElement,hover,keyDown,keyUp,manuallyDispatchProgrammaticEvent,pointerDown,press,queryOne,scroll,select,uncheck,waitFor,}=require("@odoo/hoot");const{hasTouch}=require("@web/core/browser/feature_detection");const callClick=async(clickFn,nodePromise,options)=>{const actions=[()=>clickFn(nodePromise,options)];if(options?.altKey){actions.unshift(()=>keyDown("Alt"));actions.push(()=>keyUp("Alt"));}
if(options?.ctrlKey){actions.unshift(()=>keyDown("Control"));actions.push(()=>keyUp("Control"));}
if(options?.metaKey){actions.unshift(()=>keyDown("Meta"));actions.push(()=>keyUp("Meta"));}
if(options?.shiftKey){actions.unshift(()=>keyDown("Shift"));actions.push(()=>keyUp("Shift"));}
for(const action of actions){await action();}
await animationFrame();};const dragForTolerance=async(node,distance)=>{if(distance===0){return;}
const position={x:distance||100,y:distance||100,};await hover(node,{position,relative:true});await advanceFrame();};const waitForTouchDelay=async(delay)=>{if(hasTouch()){await advanceTime(delay||500);}};let cancelCurrentDragSequence=null;const unconsumedContains=[];afterEach(async()=>{if(cancelCurrentDragSequence){await cancelCurrentDragSequence();}
if(unconsumedContains.length){const targets=unconsumedContains.map(String).join(", ");unconsumedContains.length=0;throw new Error(`called 'contains' on "${targets}" without any action: use 'waitFor' if no interaction is intended`);}});__exports.contains=contains;function contains(target,options){const consumeContains=()=>{if(!consumed){consumed=true;unconsumedContains.pop();}};const focusCurrent=async()=>{const node=await nodePromise;if(node!==getActiveElement(node)){await pointerDown(node);}
return node;};let consumed=false;unconsumedContains.push(target);const nodePromise=waitFor.as("contains")(target,{visible:true,...options});return{check:async(options)=>{consumeContains();await check(nodePromise,options);await animationFrame();},clear:async(options)=>{consumeContains();await focusCurrent();await clear({confirm:"auto",...options});await animationFrame();},click:async(options)=>{consumeContains();await callClick(click,nodePromise,options);},dblclick:async(options)=>{consumeContains();await callClick(dblclick,nodePromise,options);},drag:async(options)=>{const cancelWithDelay=async(options)=>{await cancel(options);await advanceFrame();cancelCurrentDragSequence=null;};const dropWithDelay=async(to,options)=>{if(to){await moveToWithDelay(to,options);}
await drop();await advanceFrame();cancelCurrentDragSequence=null;};const moveToWithDelay=async(to,options)=>{await moveTo(to,options);await advanceFrame();return helpersWithDelay;};consumeContains();await cancelCurrentDragSequence?.();cancelCurrentDragSequence=cancelWithDelay;const{cancel,drop,moveTo}=await drag(nodePromise,options);const helpersWithDelay={cancel:cancelWithDelay,drop:dropWithDelay,moveTo:moveToWithDelay,};await waitForTouchDelay(options?.pointerDownDuration);await dragForTolerance(nodePromise,options?.initialPointerMoveDistance);return helpersWithDelay;},dragAndDrop:async(target,dropOptions,dragOptions)=>{consumeContains();await cancelCurrentDragSequence?.();const[from,to]=await Promise.all([nodePromise,waitFor(target)]);const{drop,moveTo}=await drag(from,dragOptions);await waitForTouchDelay(dropOptions?.pointerDownDuration);await dragForTolerance(from,dropOptions?.initialPointerMoveDistance);await moveTo(to,dropOptions);await advanceFrame();await drop();await advanceFrame();},edit:async(value,options)=>{consumeContains();await focusCurrent();await edit(value,{confirm:"auto",...options});await animationFrame();},fill:async(value,options)=>{consumeContains();await focusCurrent();await fill(value,{confirm:"auto",...options});await animationFrame();},focus:async()=>{consumeContains();await focusCurrent();await animationFrame();},hover:async()=>{consumeContains();await hover(nodePromise);await animationFrame();},keyDown:async(keyStrokes,options)=>{consumeContains();await focusCurrent();await keyDown(keyStrokes,options);await animationFrame();},keyUp:async(keyStrokes,options)=>{consumeContains();await focusCurrent();await keyUp(keyStrokes,options);await animationFrame();},press:async(keyStrokes,options)=>{consumeContains();await focusCurrent();await press(keyStrokes,options);await animationFrame();},scroll:async(position)=>{consumeContains();await scroll(nodePromise,position,{scrollable:false,...options});await animationFrame();},select:async(value)=>{consumeContains();await select(value,{target:nodePromise});await animationFrame();},selectDropdownItem:async(value)=>{consumeContains();await callClick(click,queryOne(".dropdown-toggle",{root:await nodePromise}));const item=await waitFor(`.dropdown-item:contains(${value})`);await callClick(click,item);await animationFrame();},uncheck:async(options)=>{consumeContains();await uncheck(nodePromise,options);await animationFrame();},};}
__exports.defineStyle=defineStyle;function defineStyle(style){const styleEl=document.createElement("style");styleEl.textContent=style;document.head.appendChild(styleEl);after(()=>styleEl.remove());}
__exports.editAce=editAce;async function editAce(value){await manuallyDispatchProgrammaticEvent(queryOne(".ace_editor .ace_content"),"mousedown");await contains(".ace_editor textarea",{displayed:true,visible:false}).edit(value,{instantly:true,});}
return __exports;});;

/* /web/static/tests/_framework/env_test_helpers.js */
odoo.define('@web/../tests/_framework/env_test_helpers',['@odoo/hoot','@web/core/browser/router','@web/core/debug/debug_context','@web/core/l10n/translation','@web/core/registry','@web/core/utils/objects','@web/core/utils/patch','@web/env','@web/../tests/_framework/mock_server/mock_server'],function(require){'use strict';let __exports={};const{after,afterEach,beforeEach,registerDebugInfo}=require("@odoo/hoot");const{startRouter}=require("@web/core/browser/router");const{createDebugContext}=require("@web/core/debug/debug_context");const{translatedTerms,translationLoaded}=require("@web/core/l10n/translation");const{registry}=require("@web/core/registry");const{pick}=require("@web/core/utils/objects");const{patch}=require("@web/core/utils/patch");const{makeEnv,startServices}=require("@web/env");const{MockServer,makeMockServer}=require("@web/../tests/_framework/mock_server/mock_server");const registerRegistryForCleanup=(registry)=>{const content=Object.entries(registry.content).map(([key,value])=>[key,value.slice()]);registriesContent.set(registry,content);for(const subRegistry of Object.values(registry.subRegistries)){registerRegistryForCleanup(subRegistry);}};const registriesContent=new WeakMap();let currentEnv=null;beforeEach(()=>registerRegistryForCleanup(registry));afterEach(()=>restoreRegistry(registry));__exports.clearRegistry=clearRegistry;function clearRegistry(registry){registry.content={};registry.elements=null;registry.entries=null;}
__exports.getMockEnv=getMockEnv;function getMockEnv(){return currentEnv;}
__exports.getService=getService;function getService(name){return currentEnv.services[name];}
__exports.makeMockEnv=makeMockEnv;async function makeMockEnv(partialEnv,options){if(currentEnv&&!options?.makeNew){throw new Error(`cannot create mock environment: a mock environment has already been declared`);}
if(!MockServer.current){await makeMockServer();}
const env=makeEnv();Object.assign(env,partialEnv,createDebugContext(env));registerDebugInfo("env",env);if(!currentEnv){currentEnv=env;startRouter();after(()=>{currentEnv=null;if(translatedTerms[translationLoaded]){for(const key in translatedTerms){delete translatedTerms[key];}
translatedTerms[translationLoaded]=false;}});}
await startServices(env);return env;}
__exports.makeDialogMockEnv=makeDialogMockEnv;async function makeDialogMockEnv(partialEnv){return makeMockEnv({...partialEnv,dialogData:{close:()=>{},isActive:true,scrollToOrigin:()=>{},...partialEnv?.dialogData,},});}
__exports.mockService=mockService;function mockService(name,serviceFactory){const serviceRegistry=registry.category("services");const originalService=serviceRegistry.get(name,null);serviceRegistry.add(name,{...originalService,start(env,dependencies){if(typeof serviceFactory==="function"){return serviceFactory(env,dependencies);}else{const service=originalService.start(env,dependencies);if(service instanceof Promise){service.then((value)=>patch(value,serviceFactory));}else{patch(service,serviceFactory);}
return service;}},},{force:true});if(currentEnv?.services?.[name]){if(typeof serviceFactory==="function"){const dependencies=pick(currentEnv.services,...(originalService.dependencies||[]));currentEnv.services[name]=serviceFactory(currentEnv,dependencies);}else{patch(currentEnv.services[name],serviceFactory);}}}
__exports.restoreRegistry=restoreRegistry;function restoreRegistry(registry){if(registriesContent.has(registry)){clearRegistry(registry);registry.content=Object.fromEntries(registriesContent.get(registry));}
for(const subRegistry of Object.values(registry.subRegistries)){restoreRegistry(subRegistry);}}
return __exports;});;

/* /web/static/tests/_framework/hoot_module_loader.js */
(function(odoo){"use strict";if(odoo.define.name.endsWith("(hoot)")){return;}
const name=`${odoo.define.name} (hoot)`;odoo.define={[name](name,dependencies,factory){return odoo.loader.define(name,dependencies,factory,!name.endsWith(".hoot"));},}[name];})(globalThis.odoo);;

/* /web/static/tests/_framework/kanban_test_helpers.js */
odoo.define('@web/../tests/_framework/kanban_test_helpers',['@odoo/hoot','@web/../tests/_framework/component_test_helpers','@web/../tests/_framework/dom_test_helpers','@web/../tests/_framework/view_test_helpers'],function(require){'use strict';let __exports={};const{animationFrame,queryAll,queryAllAttributes,queryAllTexts,queryOne}=require("@odoo/hoot");const{getDropdownMenu}=require("@web/../tests/_framework/component_test_helpers");const{contains}=require("@web/../tests/_framework/dom_test_helpers");const{buildSelector}=require("@web/../tests/_framework/view_test_helpers");__exports.clickKanbanLoadMore=clickKanbanLoadMore;function clickKanbanLoadMore(columnIndex=0){return contains(".o_kanban_load_more button",{root:getKanbanColumn(columnIndex)}).click();}
__exports.clickKanbanRecord=clickKanbanRecord;async function clickKanbanRecord(options){await contains(buildSelector(`.o_kanban_record`,options)).click();}
__exports.createKanbanRecord=createKanbanRecord;async function createKanbanRecord(){await contains(".o_control_panel_main_buttons button.o-kanban-button-new").click();return animationFrame();}
__exports.discardKanbanRecord=discardKanbanRecord;function discardKanbanRecord(){return contains(".o_kanban_quick_create .o_kanban_cancel").click();}
__exports.editKanbanColumnName=editKanbanColumnName;function editKanbanColumnName(value){return contains(".o_column_quick_create input").edit(value);}
__exports.editKanbanRecord=editKanbanRecord;function editKanbanRecord(){return contains(".o_kanban_quick_create .o_kanban_edit").click();}
__exports.editKanbanRecordQuickCreateInput=editKanbanRecordQuickCreateInput;function editKanbanRecordQuickCreateInput(fieldName,value){return contains(`.o_kanban_quick_create .o_field_widget[name=${fieldName}] input`).edit(value,{confirm:"tab",});}
__exports.getKanbanColumn=getKanbanColumn;function getKanbanColumn(columnIndex=0,ignoreFolded=false){let selector=".o_kanban_group";if(ignoreFolded){selector+=":not(.o_column_folded)";}
return queryAll(selector).at(columnIndex);}
__exports.getKanbanColumnDropdownMenu=getKanbanColumnDropdownMenu;function getKanbanColumnDropdownMenu(columnIndex=0,ignoreFolded=false){const column=getKanbanColumn(columnIndex,ignoreFolded);return getDropdownMenu(column);}
__exports.getKanbanColumnTooltips=getKanbanColumnTooltips;function getKanbanColumnTooltips(columnIndex){queryAllAttributes;const root=columnIndex>=0&&getKanbanColumn(columnIndex);return queryAllAttributes(".o_column_progress .progress-bar","data-tooltip",{root});}
__exports.getKanbanCounters=getKanbanCounters;function getKanbanCounters(){return queryAllTexts(".o_animated_number");}
__exports.getKanbanProgressBars=getKanbanProgressBars;function getKanbanProgressBars(columnIndex=0){const column=getKanbanColumn(columnIndex);return queryAll(".o_column_progress .progress-bar",{root:column});}
__exports.getKanbanRecord=getKanbanRecord;function getKanbanRecord(options){return queryOne(buildSelector(`.o_kanban_record`,options));}
__exports.getKanbanRecordTexts=getKanbanRecordTexts;function getKanbanRecordTexts(columnIndex){const root=columnIndex>=0&&getKanbanColumn(columnIndex);return queryAllTexts(".o_kanban_record:not(.o_kanban_ghost)",{root});}
__exports.quickCreateKanbanColumn=quickCreateKanbanColumn;function quickCreateKanbanColumn(){return contains(".o_column_quick_create > .o_quick_create_folded").click();}
__exports.quickCreateKanbanRecord=quickCreateKanbanRecord;async function quickCreateKanbanRecord(columnIndex=0){await contains(".o_kanban_quick_add",{root:getKanbanColumn(columnIndex)}).click();return animationFrame();}
__exports.toggleKanbanColumnActions=toggleKanbanColumnActions;async function toggleKanbanColumnActions(columnIndex=0){const column=getKanbanColumn(columnIndex);await contains(".o_kanban_config .dropdown-toggle",{root:column,visible:false}).click();return(buttonText)=>{const menu=getDropdownMenu(column);return contains(`.dropdown-item:contains(/\\b${buttonText}\\b/i)`,{root:menu}).click();};}
__exports.toggleKanbanRecordDropdown=toggleKanbanRecordDropdown;function toggleKanbanRecordDropdown(recordIndex=0){return contains(`.o_kanban_record:eq(${recordIndex}) .o_dropdown_kanban .dropdown-toggle`,{visible:false,}).click();}
__exports.validateKanbanColumn=validateKanbanColumn;function validateKanbanColumn(){return contains(".o_column_quick_create .o_kanban_add").click();}
__exports.validateKanbanRecord=validateKanbanRecord;function validateKanbanRecord(){return contains(".o_kanban_quick_create .o_kanban_add").click();}
return __exports;});;

/* /web/static/tests/_framework/mock_browser.hoot.js */
odoo.define('@web/../tests/_framework/mock_browser.hoot',['@odoo/hoot'],function(require){'use strict';let __exports={};const{mockLocation}=require("@odoo/hoot");const READONLY_PROPERTIES=["cancelAnimationFrame","clearInterval","clearTimeout","requestAnimationFrame","setInterval","setTimeout",];__exports.mockBrowserFactory=mockBrowserFactory;function mockBrowserFactory(name,{fn}){return(...args)=>{const browserModule=fn(...args);const properties={location:{get:()=>mockLocation,set:(value)=>(mockLocation.href=value),},};for(const property of READONLY_PROPERTIES){const originalValue=browserModule.browser[property];properties[property]={configurable:false,get:()=>originalValue,};}
Object.defineProperties(browserModule.browser,properties);return browserModule;};}
return __exports;});;

/* /web/static/tests/_framework/mock_currency.hoot.js */
odoo.define('@web/../tests/_framework/mock_currency.hoot',['@web/../tests/_framework/mock_server_state.hoot'],function(require){'use strict';let __exports={};const{onServerStateChange}=require("@web/../tests/_framework/mock_server_state.hoot");const makeCurrencies=({currencies})=>Object.fromEntries(currencies.map((currency)=>[currency.id,{digits:[69,2],...currency}]));__exports.mockCurrencyFactory=mockCurrencyFactory;function mockCurrencyFactory(name,{fn}){return(requireModule,...args)=>{const currencyModule=fn(requireModule,...args);onServerStateChange(currencyModule.currencies,makeCurrencies);return currencyModule;};}
return __exports;});;

/* /web/static/tests/_framework/mock_server/mock_fields.js */
odoo.define('@web/../tests/_framework/mock_server/mock_fields',['@web/core/utils/objects','@web/../tests/_framework/mock_server/mock_server_utils'],function(require){'use strict';let __exports={};const{deepCopy}=require("@web/core/utils/objects");const{MockServerError}=require("@web/../tests/_framework/mock_server/mock_server_utils");function camelToPascal(name){return(name[0].toUpperCase()+name.slice(1).replace(R_CAMEL_CASE,(_,char)=>char.toUpperCase()));}
function makeFieldGenerator(type,{aggregator,requiredKeys=[]}={}){const constructorFnName=camelToPascal(type);const defaultDef={...DEFAULT_FIELD_PROPERTIES};if(aggregator){defaultDef.aggregator=aggregator;}
if(type!=="generic"){defaultDef.type=type;}
return{[constructorFnName](properties){const field={...defaultDef,...properties,[S_FIELD]:true,};for(const key of requiredKeys){if(!(key in field)){throw new MockServerError(`Missing key "${key}" in ${type || "generic"} field definition`);}}
if(isComputed(field)){field.readonly=properties.readonly??true;field.store=properties.store??false;}
return field;},}[constructorFnName];}
const R_CAMEL_CASE=/_([a-z])/g;const R_ENDS_WITH_ID=/_id(s)?$/i;const R_LOWER_FOLLOWED_BY_UPPER=/([a-z])([A-Z])/g;const R_SPACE_OR_UNDERSCORE=/[\s_]+/g;__exports.copyFields=copyFields;function copyFields(fields){const fieldsCopy={};for(const[fieldName,field]of Object.entries(fields)){const fieldCopy={};for(const[property,value]of Object.entries(field)){fieldCopy[property]=typeof value==="object"?deepCopy(value):value;}
fieldsCopy[fieldName]=fieldCopy;}
return fieldsCopy;}
__exports.isComputed=isComputed;function isComputed(field){return globalThis.Boolean(field.compute||field.related);}
__exports.getFieldDisplayName=getFieldDisplayName;function getFieldDisplayName(value){const str=String(value).replace(R_ENDS_WITH_ID,"$1").replace(R_LOWER_FOLLOWED_BY_UPPER,(_,a,b)=>`${a} ${b.toLowerCase()}`).replace(R_SPACE_OR_UNDERSCORE," ").trim();return str[0].toUpperCase()+str.slice(1);}
const DEFAULT_MONEY_FIELD_VALUES=__exports.DEFAULT_MONEY_FIELD_VALUES={monetary:()=>0,};const DEFAULT_RELATIONAL_FIELD_VALUES=__exports.DEFAULT_RELATIONAL_FIELD_VALUES={many2many:()=>[],many2one:()=>false,many2one_reference:()=>false,one2many:()=>[],};const DEFAULT_SELECTION_FIELD_VALUES=__exports.DEFAULT_SELECTION_FIELD_VALUES={reference:()=>false,selection:()=>false,};const DEFAULT_STANDARD_FIELD_VALUES=__exports.DEFAULT_STANDARD_FIELD_VALUES={binary:()=>false,boolean:()=>false,char:()=>false,date:()=>false,datetime:()=>false,float:()=>0,html:()=>false,number:()=>0,image:()=>false,integer:()=>0,json:()=>false,properties:()=>false,properties_definition:()=>false,text:()=>false,};const DEFAULT_FIELD_VALUES=__exports.DEFAULT_FIELD_VALUES={...DEFAULT_MONEY_FIELD_VALUES,...DEFAULT_RELATIONAL_FIELD_VALUES,...DEFAULT_SELECTION_FIELD_VALUES,...DEFAULT_STANDARD_FIELD_VALUES,};const DEFAULT_FIELD_PROPERTIES=__exports.DEFAULT_FIELD_PROPERTIES={readonly:false,required:false,searchable:true,sortable:true,store:true,groupable:true,};const S_FIELD=__exports.S_FIELD=Symbol("field");const S_SERVER_FIELD=__exports.S_SERVER_FIELD=Symbol("field");const Binary=__exports.Binary=makeFieldGenerator("binary");const Boolean=__exports.Boolean=makeFieldGenerator("boolean");const Char=__exports.Char=makeFieldGenerator("char");const Date=__exports.Date=makeFieldGenerator("date");const Datetime=__exports.Datetime=makeFieldGenerator("datetime");const Float=__exports.Float=makeFieldGenerator("float",{aggregator:"sum",});const Generic=__exports.Generic=makeFieldGenerator("generic");const Html=__exports.Html=makeFieldGenerator("html");const Image=__exports.Image=makeFieldGenerator("image");const Integer=__exports.Integer=makeFieldGenerator("integer",{aggregator:"sum",});const Json=__exports.Json=makeFieldGenerator("json");const Many2many=__exports.Many2many=makeFieldGenerator("many2many",{requiredKeys:["relation"],});const Many2one=__exports.Many2one=makeFieldGenerator("many2one",{requiredKeys:["relation"],});const Many2oneReference=__exports.Many2oneReference=makeFieldGenerator("many2one_reference",{requiredKeys:["model_field","relation"],});const Monetary=__exports.Monetary=makeFieldGenerator("monetary",{aggregator:"sum",requiredKeys:["currency_field"],});const One2many=__exports.One2many=makeFieldGenerator("one2many",{requiredKeys:["relation"],});const Properties=__exports.Properties=makeFieldGenerator("properties",{requiredKeys:["definition_record","definition_record_field"],});const PropertiesDefinition=__exports.PropertiesDefinition=makeFieldGenerator("properties_definition");const Reference=__exports.Reference=makeFieldGenerator("reference",{requiredKeys:["selection"],});const Selection=__exports.Selection=makeFieldGenerator("selection",{requiredKeys:["selection"],});const Text=__exports.Text=makeFieldGenerator("text");return __exports;});;

/* /web/static/tests/_framework/mock_server/mock_model.js */
odoo.define('@web/../tests/_framework/mock_server/mock_model',['@odoo/hoot','@web/core/domain','@web/core/l10n/dates','@web/core/utils/arrays','@web/core/utils/objects','@web/../tests/_framework/mock_server/mock_fields','@web/../tests/_framework/mock_server/mock_server','@web/../tests/_framework/mock_server/mock_server_utils'],function(require){'use strict';let __exports={};const{after,createJobScopedGetter}=require("@odoo/hoot");const{Domain}=require("@web/core/domain");const{deserializeDate,deserializeDateTime,parseDateTime,serializeDate,serializeDateTime,}=require("@web/core/l10n/dates");const{ensureArray,intersection,isIterable,unique}=require("@web/core/utils/arrays");const{deepCopy,isObject,pick}=require("@web/core/utils/objects");const fields=require("@web/../tests/_framework/mock_server/mock_fields");const{MockServer}=require("@web/../tests/_framework/mock_server/mock_server");const{getKwArgs,getRecordQualifier,makeKwArgs,makeServerError,MockServerError,safeSplit,}=require("@web/../tests/_framework/mock_server/mock_server_utils");const{DEFAULT_FIELD_VALUES,DEFAULT_RELATIONAL_FIELD_VALUES,DEFAULT_SELECTION_FIELD_VALUES,S_FIELD,copyFields,isComputed,}=fields;function aggregateFields(aggregatedFields,group,records){for(const[field,name,aggregator]of aggregatedFields){switch(field.type){case"integer":case"float":{if(aggregator==="array_agg"){group[name]=records.map((r)=>r[field.name]);}else if(records.length){group[name]=0;for(const r of records){group[name]+=r[field.name];}}else{group[name]=false;}
break;}
case"many2one":case"reference":{const ids=records.map((r)=>r[field.name]);if(aggregator==="array_agg"){group[name]=ids.map((id)=>(id?id:null));}else{const uniqueIds=unique(ids).filter(Boolean);group[name]=uniqueIds.length;}
break;}
case"boolean":{if(aggregator==="array_agg"){group[name]=records.map((r)=>r[field.name]);}else if(aggregator==="bool_or"){group[name]=records.some((r)=>Boolean(r[field.name]));}else if(aggregator==="bool_and"){group[name]=records.every((r)=>Boolean(r[field.name]));}
break;}}}}
function assignArray(target,...arrays){for(const array of arrays){for(let i=0;i<array.length;i++){target[i]=array[i];}}
target.length=Math.max(...arrays.map((array)=>array.length));return target;}
function convertToOnChange(model,values,specification){for(const[fname,val]of Object.entries(values)){const field=model._fields[fname];if(isM2OField(field.type)&&typeof val==="number"){values[fname]=getRelation(field).web_read(val,specification[fname].fields||{})[0];}else if(isX2MField(field)){const coModel=getRelation(field);for(const cmd of val){switch(cmd[0]){case 0:case 1:cmd[2]=convertToOnChange(coModel,cmd[2],specification[fname].fields||{});break;case 4:cmd[2]=coModel.web_read(cmd[1],specification[fname].fields||{})[0];}}}else if(field.type==="reference"&&val){const[modelName,id]=getReferenceValue(val);const result=model.env[modelName].web_read(id,specification[fname].fields||{});values[fname]={...result[0],id:{id,model:modelName}};}}
return values;}
function createRawInstance(ModelClass){modelInstanceLock++;const model=new ModelClass();modelInstanceLock--;return model;}
function fieldNotFoundError(modelName,fieldName,consequence){let message=`Cannot find a definition for field "${fieldName}" in model "${modelName}"`;if(consequence){message+=`: ${consequence}`;}
return new MockServerError(message);}
function findView(model,viewType,viewId){const availableViews=Object.create(null);for(const[rawKey,arch]of Object.entries(model._views)){availableViews[getViewKey(...safeSplit(rawKey))]=arch;}
for(const[id,arch]of Object.entries(inlineViewArchs[model._name]||{})){if(arch||!availableViews[id]){availableViews[id]=arch;}}
let viewKey=getViewKey(viewType,viewId);if(!(viewKey in availableViews)){if(typeof viewId==="number"){throw viewNotFoundError(model._name,viewType,viewId);}
viewKey=Object.keys(availableViews).filter((key)=>key.startsWith(viewType)).sort()[0];viewId=safeSplit(viewKey)[1];}
const arch=availableViews[viewKey]||`<${viewType} />`;const actualViewId=Number(viewId)||false;return[arch,actualViewId];}
function formatFieldValue(fields,groupByField,val){if(val===false||val===undefined){return false;}
const[fieldName,aggregateFunction="month"]=safeSplit(groupByField,":");const{type}=fields[fieldName];if(type==="date"){const date=deserializeDate(String(val));return aggregateFunction in DATE_FORMAT?DATE_FORMAT[aggregateFunction](date):date.toFormat("MMMM yyyy");}else if(type==="datetime"){const date=deserializeDateTime(val);return aggregateFunction in DATETIME_FORMAT?DATETIME_FORMAT[aggregateFunction](date):date.toFormat("MMMM yyyy");}else if(Array.isArray(val)){return val.length!==0&&(isX2MField(type)?val:val[0]);}else{return val;}}
function getDateSortingValue(group,fieldName){let max=null;for(const groupedBy in group.__range){if(groupedBy.startsWith(fieldName)){const value=group.__range[groupedBy].from;if(!value){return false;}
const ts=new Date(value).getTime();if(ts>max){max=ts;}}}
return max??false;}
function getDateNumberSortingValue(group,fieldName){let max=-1;let value=false;for(const groupedBy in group){if(groupedBy.startsWith(fieldName)){const[,granularity]=groupedBy.split(":");const index=READ_GROUP_NUMBER_GRANULARITY.indexOf(granularity);if(index!==-1&&index>max){max=index;value=group[groupedBy];}}}
return value;}
function isEmptyValue(value){if(!value){return true;}
if(Array.isArray(value)){return value.length===0;}
if(typeof value==="object"){return Object.keys(value).length===0;}
return false;}
function getModelDefinition(previous,constructor){const model=createRawInstance(constructor);model._name||=constructor.getModelName(model);if(previous&&!modelInstanceLock){if(constructor===previous.constructor){for(const[key,map]of INHERITED_PRIMITIVE_KEYS){model[key]=map?map(previous[key]):previous[key];}
for(const[key,map]of INHERITED_OBJECT_KEYS){Object.assign(model[key],map?map(previous[key]):previous[key]);}
assignArray(model._records,deepCopy(previous._records));}else{for(const[key,map]of INHERITED_PRIMITIVE_KEYS){if(isEmptyValue(model[key])){model[key]=map?map(previous[key]):previous[key];}}
for(const[key,map]of INHERITED_OBJECT_KEYS){const previousValue=map?map(previous[key]):previous[key];for(const subKey in previousValue){if(isEmptyValue(model[key][subKey])){model[key][subKey]=previousValue[subKey];}}}
if(!model._records.length){assignArray(model._records,deepCopy(previous._records));}}}
for(const[fieldName,fieldDef]of Object.entries(model._fields)){if(!fieldDef){delete model._fields[fieldName];continue;}
validateFieldDefinition(fieldName,fieldDef);}
for(const[fieldName,fieldDef]of Object.entries(model)){if(!fieldDef?.[S_FIELD]){continue;}
model._fields[fieldName]||=validateFieldDefinition(fieldName,fieldDef);delete model[fieldName];}
return model;}
function getOrderByField({_fields,_name},fieldNameSpec){const fieldName=fieldNameSpec?.split(":")[0]||("sequence"in _fields?"sequence":"id");if(!(fieldName in _fields)){throw fieldNotFoundError(_name,fieldName,"could not order records");}
return _fields[fieldName];}
function getReferenceValue(value){const[modelName,id]=safeSplit(value);return[modelName,JSON.parse(id)];}
function getRelation(field,record={}){let relation;if(field.relation){relation=field.relation;}else if(field.type==="many2one_reference"){relation=record[field.model_field];}
const comodel=relation||record[field.model_name_ref_fname];return comodel&&MockServer.env[comodel];}
function getTag(node){if(typeof node==="string"){return node;}else if(node){return getTag(node.nodeName.toLowerCase());}else{return node;}}
function getView(model,args,kwargs){let[requestViewId,viewType]=args;if(!requestViewId){const contextKey=`${viewType}_view_ref`;if(kwargs.context&&contextKey in kwargs.context){requestViewId=kwargs.context[contextKey];}}
const[arch,viewId]=findView(model,viewType,requestViewId);const view=parseView(model,{arch});if(kwargs.options?.toolbar){view.toolbar=model._toolbar;}
if(viewId!==undefined){view.id=viewId;}
return view;}
function getViewFields(model,viewType,models){switch(viewType){case"form":case"kanban":case"list":{for(const fieldNames of Object.values(models)){fieldNames.add("id");fieldNames.add("write_date");}
break;}
case"graph":{for(const{name,type}of Object.values(model._fields)){if(["float","integer"].includes(type)){models[model._name].add(name);}}
break;}
case"pivot":{for(const{name,type}of Object.values(model._fields)){if(["boolean","char","date","datetime","many2many","many2one","many2one_reference","selection",].includes(type)){models[model._name].add(name);}}
break;}
case"search":{models[model._name]=new Set(Object.keys(model._fields));break;}}
return models;}
function getViewKey(viewType,viewId){const nViewId=viewId&&!isNaN(viewId)?Number(viewId):viewId;return[viewType,nViewId||false].join(",");}
function isDateField(field){const fieldType=typeof field==="string"?field:field.type;return fieldType==="date"||fieldType==="datetime";}
function isM2OField(field){const fieldType=typeof field==="string"?field:field.type;return fieldType==="many2one"||fieldType==="many2one_reference";}
function isRelationalView(viewType){return["form","kanban","list"].includes(viewType);}
function isValidCommand(command){const[action,id,data]=command;if(!command.length){return false;}
if(action<0||action>6){return false;}
if(command.length>1&&!(id===false||Number.isInteger(id))){return false;}
if(command.length>2&&typeof data!=="object"){return false;}
return command.length<=3;}
function isValidFieldValue(record,fieldDef){const value=record[fieldDef.name];if(value===false){return true;}
switch(fieldDef.type){case"binary":case"char":case"html":case"text":{return typeof value==="string";}
case"json":{return typeof value==="string"||typeof value==="object";}
case"boolean":{return typeof value==="boolean";}
case"date":{return R_DATE.test(value);}
case"datetime":{return R_DATE_TIME.test(value);}
case"float":case"monetary":{return typeof value==="number";}
case"integer":{return Number.isInteger(value);}
case"many2many":case"one2many":{return(Array.isArray(value)&&value.every((id)=>{if(Array.isArray(id)){return isValidCommand(id);}else{return isValidId(id,fieldDef,record);}}));}
case"many2one":case"many2one_reference":{return isValidId(value,fieldDef,record);}
case"properties":{return isObject(value);}
case"properties_definition":{return value.every((def)=>typeof def.name==="string"&&typeof def.type==="string");}
case"reference":{const[modelName,id]=getReferenceValue(value);return(fieldDef.selection.some(([value])=>value===modelName)&&isValidId(id,{...fieldDef,relation:modelName},record));}
case"selection":{return fieldDef.selection.some(([value])=>value===value);}
default:{return true;}}}
function isValidId(id,field,record){if(id===false){return true;}
if(!Number.isInteger(id)){return false;}
const rel=getRelation(field,record);return rel&&rel.some((record)=>record.id===id);}
function isViewEditable(element,modelName){switch(getTag(element)){case"form":return true;case"list":return element.getAttribute("editable")||element.getAttribute("multi_edit");case"field":{const fname=element.getAttribute("name");const field=MockServer.env[modelName]._fields[fname];return!field.readonly&&!/^(true|1)$/i.test(element.getAttribute("readonly"));}
default:return false;}}
function isX2MField(field){const fieldType=typeof field==="string"?field:field.type;return fieldType==="many2many"||fieldType==="one2many";}
function orderByField(model,orderBy,records){if(!records){records=model;}
if(!orderBy){orderBy=model._order;}
const orderBys=safeSplit(orderBy);const[fieldNameSpec,direction="ASC"]=safeSplit(orderBys.pop()," ");const field=getOrderByField(model.env[model._name],fieldNameSpec);let valuesMap;if(field.type in DEFAULT_RELATIONAL_FIELD_VALUES){let valueLength;const coModel=getRelation(field);const coField=getOrderByField(coModel);if(isX2MField(field)){orderByField(coModel);if(["float","integer"].includes(coField.type)){valueLength=coModel.reduce((longest,record)=>Math.max(longest,String(record[coField.name]).length),0);}}
valuesMap=new Map(coModel.map((record)=>{const value=record[coField.name];if(valueLength){const strValue=String(value);return[record.id,strValue.padStart(valueLength,"0")];}else{return[record.id,value];}}));}else if(field.type in DEFAULT_SELECTION_FIELD_VALUES){valuesMap=new Map(field.selection.map((v,i)=>[v[0],i]));}
const sortedRecords=records.sort((r1,r2)=>{let v1=r1[field.name];let v2=r2[field.name];switch(field.type){case"boolean":{v1=Number(v1);v2=Number(v2);break;}
case"many2one":case"many2one_reference":{v1&&=valuesMap.get(v1[0]);v2&&=valuesMap.get(v2[0]);break;}
case"many2many":case"one2many":{v1&&=v1.map((id)=>valuesMap.get(id)).join("");v2&&=v2.map((id)=>valuesMap.get(id)).join("");break;}
case"date":case"datetime":{if(r1.__range&&r2.__range){v1=getDateSortingValue(r1,field.name);v2=getDateSortingValue(r2,field.name);}else{v1=getDateNumberSortingValue(r1,field.name);v2=getDateNumberSortingValue(r2,field.name);}
break;}
case"reference":case"selection":{v1&&=valuesMap.get(v1);v2&&=valuesMap.get(v2);break;}}
let result;if(v1===false){result=1;}else if(v2===false){result=-1;}else{if(!["boolean","number","string"].includes(typeof v1)||typeof v1!==typeof v2){throw new MockServerError(`Cannot order by field "${field.name}" in model "${
                        model._name
                    }": values must be of the same primitive type (got ${typeof v1} and ${typeof v2})`);}
result=v1>v2?1:v1<v2?-1:0;}
return direction==="DESC"?-result:result;});if(orderBys.length){return orderByField(model,orderBys.join(","),sortedRecords);}
return sortedRecords;}
function parseView(model,params){const processedNodes=params.processedNodes||[];const{arch}=params;const level=params.level||0;const editable=params.editable||true;const fields=copyFields(model._fields);const{_onChanges}=model;const fieldNodes={};const groupbyNodes={};const relatedModels={[model._name]:new Set()};const doc=typeof arch==="string"?domParser.parseFromString(arch,"text/xml").documentElement:arch;const viewType=getTag(doc);const isEditable=editable&&isViewEditable(doc,model._name);traverseElement(doc,(node)=>{if(node.nodeType!==Node.ELEMENT_NODE){return false;}
for(const attr of["required","readonly","invisible","column_invisible"]){if(/^(true|1)$/i.test(node.getAttribute(attr))){node.setAttribute(attr,"True");}}
const isField=getTag(node)==="field";const isGroupby=getTag(node)==="groupby";if(isField){const fieldName=node.getAttribute("name");fieldNodes[fieldName]={node,isInvisible:/^(true|1)$/i.test(node.getAttribute("invisible")),isEditable:isEditable&&isViewEditable(node,model._name),};const field=fields[fieldName];if(!field){throw fieldNotFoundError(model._name,fieldName);}}else if(isGroupby&&!processedNodes.includes(node)){const groupbyName=node.getAttribute("name");fieldNodes[groupbyName]={node};groupbyNodes[groupbyName]=node;}
if(isGroupby&&!processedNodes.includes(node)){return false;}
return!isField;});for(const fieldName in fieldNodes){relatedModels[model._name].add(fieldName);}
for(const[name,{node,isInvisible,isEditable}]of Object.entries(fieldNodes)){const field=fields[name];if(isEditable&&(isM2OField(field)||isX2MField(field))){const canCreate=node.getAttribute("can_create");node.setAttribute("can_create",canCreate||"true");const canWrite=node.getAttribute("can_write");node.setAttribute("can_write",canWrite||"true");}
if(isX2MField(field)){const relModel=getRelation(field);if(viewType==="form"&&level===0&&!node.getAttribute("widget")&&!isInvisible){const inlineViewTypes=[...node.childNodes].map(getTag);const missingViewtypes=[];const nodeMode=getTag(node.getAttribute("mode"));if(!intersection(inlineViewTypes,safeSplit(nodeMode||"kanban,list")).length){missingViewtypes.push(safeSplit(nodeMode||"list")[0]);}
for(const type of missingViewtypes){const[arch]=findView(relModel,type,false);node.appendChild(domParser.parseFromString(arch,"text/xml").documentElement);}}
for(const childNode of node.childNodes){if(childNode.nodeType===Node.ELEMENT_NODE){const{models}=parseView(relModel,{arch:childNode,editable:isEditable,level:level+1,processedNodes,});for(const[modelName,fields]of Object.entries(models)){relatedModels[modelName]||=new Set();for(const field of fields){relatedModels[modelName].add(field);}}}}}
if(isRelationalView(viewType)&&name in _onChanges){node.setAttribute("on_change","1");}}
for(const[name,node]of Object.entries(groupbyNodes)){const field=fields[name];if(!isM2OField(field)){throw new MockServerError("Cannot group: 'groupby' can only target many2one fields");}
field.views={};const coModel=getRelation(field);processedNodes.push(node);const{models}=parseView(coModel,{arch:node,editable:false,processedNodes,});for(const[modelName,fields]of Object.entries(models)){relatedModels[modelName]||=new Set();for(const field of fields){relatedModels[modelName].add(field);}}}
const processedArch=xmlSerializer.serializeToString(doc);const fieldsInView={};for(const field of Object.values(fields)){if(field.name in fieldNodes){fieldsInView[field.name]=field;}}
return{arch:processedArch,model:model._name,models:getViewFields(model,viewType,relatedModels),type:viewType,};}
function searchPanelDomainImage(model,fieldName,domain,setCount=false,limit=false){const field=model._fields[fieldName];let groupIdName;if(isM2OField(field)){groupIdName=(value)=>value||[false,undefined];}else if(field.type==="selection"){const selection={};for(const[value,label]of model._fields[fieldName].selection){selection[value]=label;}
groupIdName=(value)=>[value,selection[value]];}
domain=new Domain([...domain,[fieldName,"!=",false]]).toList();const groups=model.read_group(domain,[fieldName],[fieldName],makeKwArgs({limit}));const domainImage=new Map();for(const group of groups){const[id,display_name]=groupIdName(group[fieldName]);const values={id,display_name};if(setCount){values.__count=group[fieldName+"_count"];}
domainImage.set(id,values);}
return domainImage;}
function searchPanelFieldImage(model,fieldName,kwargs){const enableCounters=kwargs.enable_counters;const onlyCounters=kwargs.only_counters;const extraDomain=kwargs.extra_domain||[];const normalizedExtra=new Domain(extraDomain).toList();const noExtra=JSON.stringify(normalizedExtra)==="[]";const modelDomain=kwargs.model_domain||[];const countDomain=new Domain([...modelDomain,...extraDomain]).toList();const limit=kwargs.limit;const setLimit=kwargs.set_limit;if(onlyCounters){return searchPanelDomainImage(model,fieldName,countDomain,true);}
const modelDomainImage=searchPanelDomainImage(model,fieldName,modelDomain,enableCounters&&noExtra,setLimit&&limit);if(enableCounters&&!noExtra){const countDomainImage=searchPanelDomainImage(model,fieldName,countDomain,true);for(const[id,values]of modelDomainImage.entries()){const element=countDomainImage.get(id);values.__count=element?element.__count:0;}}
return modelDomainImage;}
function searchPanelGlobalCounters(valuesRange,parentName){const localCounters=[...valuesRange.keys()].map((id)=>valuesRange.get(id).__count);for(let[id,values]of valuesRange.entries()){const count=localCounters[id];if(count){let parent_id=values[parentName];while(parent_id){values=valuesRange.get(parent_id);values.__count+=count;parent_id=values[parentName];}}}}
function searchPanelSanitizedParentHierarchy(model,parentName,ids){const allowedRecords={};for(const record of model){allowedRecords[record.id]=record;}
const recordsToKeep={};for(const id of ids){const ancestorChain={};let recordId=id;let chainIsFullyIncluded=true;while(chainIsFullyIncluded&&recordId){const knownStatus=recordsToKeep[recordId];if(knownStatus!==undefined){chainIsFullyIncluded=knownStatus;break;}
const record=allowedRecords[recordId];if(record){ancestorChain[recordId]=record;recordId=record[parentName]&&record[parentName][0];}else{chainIsFullyIncluded=false;}}
for(const id in ancestorChain){recordsToKeep[id]=chainIsFullyIncluded;}}
return model.filter((rec)=>recordsToKeep[rec.id]);}
function searchPanelSelectionRange(model,fieldName,kwargs){const enableCounters=kwargs.enable_counters;const expand=kwargs.expand;let domainImage;if(enableCounters||!expand){domainImage=searchPanelFieldImage(model,fieldName,{...kwargs,only_counters:expand,});}
if(!expand){return[...domainImage.values()];}
const selection=model._fields[fieldName].selection;const selectionRange=[];for(const[value,label]of selection){const values={id:value,display_name:label,};if(enableCounters){values.__count=domainImage.get(value)?domainImage.get(value).__count:0;}
selectionRange.push(values);}
return selectionRange;}
function toIdDisplayName(record){return record&&[record.id,record.display_name];}
function traverseElement(node,callback){if(callback(node)){for(const child of node.childNodes){traverseElement(child,callback);}}}
function updateComodelRelationalFields(model,record,originalRecord){for(const fname in record){const field=model._fields[fname];const coModel=getRelation(field,record);const inverseFieldName=field.inverse_fname_by_model_name&&field.inverse_fname_by_model_name[coModel?._name];if(!inverseFieldName){continue;}
const relatedRecordIds=ensureArray(record[fname]);const comodelInverseField=coModel._fields[inverseFieldName];if(record[fname]){for(const relatedRecordId of relatedRecordIds){let inverseFieldNewValue=record.id;const relatedRecord=coModel.find((record)=>record.id===relatedRecordId);const relatedFieldValue=relatedRecord&&relatedRecord[inverseFieldName];if(relatedFieldValue===undefined||relatedFieldValue===record.id||(field.type!=="one2many"&&relatedFieldValue.includes(record.id))){continue;}
if(Array.isArray(relatedFieldValue)){inverseFieldNewValue=[...relatedFieldValue,record.id];}
const data={[inverseFieldName]:inverseFieldNewValue};if(comodelInverseField.type==="many2one_reference"){data[comodelInverseField.model_name_ref_fname]=model._name;}
coModel._write(data,relatedRecordId);}}else if(field.type==="many2one_reference"){const model_many2one_field=comodelInverseField.inverse_fname_by_model_name[model._name];model._write({[model_many2one_field]:false},record.id);}
if(originalRecord){const originalRecordIds=ensureArray(originalRecord[fname]);const removedRecordIds=originalRecordIds.filter((recordId)=>Number.isInteger(recordId)&&!relatedRecordIds.includes(recordId));for(const removedRecordId of removedRecordIds){const removedRecord=coModel.find((record)=>record.id===removedRecordId);if(!removedRecord){continue;}
let inverseFieldNewValue=false;if(Array.isArray(removedRecord[inverseFieldName])){inverseFieldNewValue=removedRecord[inverseFieldName].filter((id)=>id!==record.id);}
coModel._write({[inverseFieldName]:inverseFieldNewValue.length?inverseFieldNewValue:false,},removedRecordId);}}}}
function validateFieldDefinition(fieldName,fieldDef){if(fieldDef[S_FIELD]&&fieldDef.name){throw new MockServerError(`Cannot set the name of field "${fieldName}" from its definition: got "${fieldDef.name}"`);}
delete fieldDef[S_FIELD];return fieldDef;}
function viewNotFoundError(modelName,viewType,viewId,consequence){let message=`Cannot find an arch for view "${viewType}" with ID ${JSON.stringify(
        viewId
    )} in model "${modelName}"`;if(consequence){message+=`: ${consequence}`;}
return new MockServerError(message);}
const DATE_FORMAT={day_of_month:(date)=>date.day,day_of_week:(date)=>date.weekday%7,day_of_year:(date)=>date.ordinal,day:(date)=>date.toFormat("yyyy-MM-dd"),iso_week_number:(date)=>date.weekNumber,month_number:(date)=>date.month,quarter_number:(date)=>date.quarter,quarter:(date)=>`Q${date.toFormat("q yyyy")}`,week:(date)=>`W${date.toFormat("WW kkkk")}`,year_number:(date)=>date.year,year:(date)=>date.toFormat("yyyy"),};const DATETIME_FORMAT={...DATE_FORMAT,hour_number:(date)=>date.hour,hour:(date)=>date.toFormat("HH:00 dd MMM yyyy"),minute_number:(date)=>date.minute,second_number:(date)=>date.second,};const INHERITED_OBJECT_KEYS=[["_computes",null],["_fields",copyFields],["_onChanges",null],["_toolbar",deepCopy],["_views",null],];const INHERITED_PRIMITIVE_KEYS=[["_description",null],["_inherit",null],["_order",null],["_parent_name",null],["_rec_name",null],["_related",(set)=>new Set(set)],];const READ_GROUP_NUMBER_GRANULARITY=["day_of_month","day_of_week","day_of_year","hour_number","iso_week_number","minute_number","month_number","quarter_number","second_number","year_number",];const VALID_AGGREGATE_FUNCTIONS=["array_agg","avg","bool_and","bool_or","count","count_distinct","max","min","sum",];const R_AGGREGATE_FUNCTION=/(\w+)(?::(\w+)(?:\((\w+)\))?)?/;const R_CAMEL_CASE=/([a-z])([A-Z])/g;const R_DATE=/\d{4}-\d{2}-\d{2}/;const R_DATE_TIME=/\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?/;const inlineViewArchs=Object.create(null);const domParser=new DOMParser();const xmlSerializer=new XMLSerializer();let modelInstanceLock=0;__exports.registerInlineViewArchs=registerInlineViewArchs;function registerInlineViewArchs(modelName,archs){if(!inlineViewArchs[modelName]){inlineViewArchs[modelName]=Object.create(null);after(()=>delete inlineViewArchs[modelName]);}
const modelViews=inlineViewArchs[modelName];for(const[rawKey,arch]of Object.entries(archs)){modelViews[getViewKey(...safeSplit(rawKey))]=arch;}}
const Model=__exports.Model=class Model extends Array{static definitionGetter=null;static get definition(){this.definitionGetter||=createJobScopedGetter(getModelDefinition);return this.definitionGetter(this);}
static get _description(){return this.definition._description;}
static set _description(value){this.definition._description=value;}
static get _fields(){return this.definition._fields;}
static set _fields(value){this.definition._fields=value;}
static get _filters(){return this.definition._filters;}
static set _filters(value){this.definition._filters=value;}
static get _inherit(){return this.definition._inherit;}
static set _inherit(value){this.definition._inherit=value;}
static get _name(){return this.definition._name;}
static set _name(value){this.definition._name=value;}
static get _onChanges(){return this.definition._onChanges;}
static set _onChanges(value){this.definition._onChanges=value;}
static get _order(){return this.definition._order;}
static set _order(value){this.definition._order=value;}
static get _parent_name(){return this.definition._parent_name;}
static set _parent_name(value){this.definition._parent_name=value;}
static get _rec_name(){return this.definition._rec_name;}
static set _rec_name(value){this.definition._rec_name=value;}
static get _records(){return this.definition._records;}
static set _records(value){assignArray(this.definition._records,value);}
static get _toolbar(){return this.definition._toolbar;}
static set _toolbar(value){this.definition._toolbar=value;}
static get _views(){return this.definition._views;}
static set _views(value){this.definition._views=value;}
static getModelName(instance){instance||=createRawInstance(this);return(instance._name||instance._inherit||(this.name?this.name.replace(R_CAMEL_CASE,"$1.$2").toLowerCase():"anonymous"));}
_computes={};_description="";_fields={};_filters=[];_inherit=null;_name="";_onChanges={};_order="id";_parent_name="parent_id";_rec_name=null;_records=[];_related=new Set();_toolbar={};_views={};get env(){return MockServer.env;}
id=fields.Integer({readonly:true});display_name=fields.Char({compute:"_compute_display_name"});create_date=fields.Datetime({string:"Created on",readonly:true,default:()=>new Date().toISOString().slice(0,19).replace("T"," "),});write_date=fields.Datetime({string:"Last Modified on",readonly:true,default:(record)=>record.create_date,});static[Symbol.iterator](){return this.definition[Symbol.iterator]();}
constructor(){super(...arguments);if(!modelInstanceLock){const modelInstance=this.constructor.definition;this._computes=modelInstance._computes;this._description=modelInstance._description;this._fields=modelInstance._fields;this._inherit=modelInstance._inherit;this._name=modelInstance._name;this._onChanges=modelInstance._onChanges;this._order=modelInstance._order;this._parent_name=modelInstance._parent_name;this._rec_name=modelInstance._rec_name;this._related=modelInstance._related;this._views=modelInstance._views;}}
action_archive(idOrIds){const kwargs=getKwArgs(arguments,"ids");({ids:idOrIds}=kwargs);return this.write(idOrIds,{active:false},kwargs);}
action_unarchive(idOrIds){const kwargs=getKwArgs(arguments,"ids");({ids:idOrIds}=kwargs);return this.write(idOrIds,{active:true},kwargs);}
browse(idOrIds){const ids=ensureArray(idOrIds);const records=new this.constructor();if(ids.length>1){const recordSet=new Map();for(const id of ids){recordSet.set(id,undefined);}
for(const record of this){if(recordSet.has(record.id)){recordSet.set(record.id,record);}}
for(const record of recordSet.values()){if(record){records.push(record);}}}else if(ids.length===1){const record=this.find((rec)=>rec.id===ids[0]);if(record){records.push(record);}}
return records;}
copy(idOrIds,defaultValues){({ids:idOrIds,default:defaultValues}=getKwArgs(arguments,"ids","default"));return ensureArray(idOrIds).map((id)=>{const copyId=this._getNextId();const originalRecord=this.find((record)=>record.id===id);this.push({...originalRecord,...defaultValues,id:copyId,display_name:`${originalRecord.display_name} (copy)`,});return copyId;});}
create(valuesList){const kwargs=getKwArgs(arguments,"vals_list");({vals_list:valuesList}=kwargs);const shouldReturnList=isIterable(valuesList);const allValues=shouldReturnList?valuesList:[valuesList];const ids=[];for(const values of allValues){if("id"in values){throw new MockServerError(`Cannot create a record with a given ID value`);}
const record={id:this._getNextId()};ids.push(record.id);this.push(record);this._applyDefaults(values,kwargs.context);this._write(values,record.id);}
this.browse(ids)._applyComputesAndValidate();return shouldReturnList?ids:ids[0];}
default_get(fields){const kwargs=getKwArgs(arguments,"fields_list");({fields_list:fields}=kwargs);const result={};for(const fieldName of fields){if(fieldName==="id"){continue;}
const field=this._fields[fieldName];const key="default_"+fieldName;if(kwargs.context&&key in kwargs.context){if(isX2MField(field)){const ids=kwargs.context[key]||[];result[fieldName]=ids.map(Command.link);}else{result[fieldName]=kwargs.context[key];}
continue;}
if("default"in field){result[fieldName]=field.default;continue;}else{if(!(field.type in DEFAULT_FIELD_VALUES)){throw new MockServerError(`Missing default value for field type "${field.type}"`);}
result[fieldName]=DEFAULT_FIELD_VALUES[field.type]();}}
for(const fieldName in result){const field=this._fields[fieldName];if(isM2OField(field)&&result[fieldName]){if(!isValidId(result[fieldName],field,result)){delete result[fieldName];}}}
return result;}
fields_get(fieldNames,attributes){const kwargs=getKwArgs(arguments,"allfields","attributes");({allfields:fieldNames,attributes}=kwargs);const fields=fieldNames?pick(this._fields,...fieldNames):this._fields;if(!attributes){return fields;}
return fields.map((field)=>pick(field,...attributes));}
get_views(views,options){const kwargs=getKwArgs(arguments,"views","options");({views,options={}}=kwargs);const models={};const result={};const modelFields={};views.forEach(([viewId,viewType])=>{result[viewType]=getView(this,[viewId,viewType],kwargs);for(const[modelName,fields]of Object.entries(result[viewType].models)){modelFields[modelName]||={fields:new Set()};for(const field of fields){modelFields[modelName].fields.add(field);}}
delete result[viewType].models;});for(const[modelName,value]of Object.entries(modelFields)){models[modelName]={fields:this.env[modelName].fields_get(value.fields)};}
if(options.load_filters&&"search"in result){result["search"].filters=this._filters;}
return{models,views:result};}
name_create(name){const kwargs=getKwArgs(arguments,"name");({name}=kwargs);const values={[this._rec_name]:name,display_name:name};const[id]=this.create([values],kwargs);return[id,kwargs.name];}
name_search(name,domain,operator,limit){const kwargs=getKwArgs(arguments,"name","args","operator","limit");({name="",args:domain=[],operator="ilike",limit=100}=kwargs);const actualDomain=new Domain(domain);const result=[];for(const record of this){const isInDomain=actualDomain.contains(record);if(isInDomain&&(!name||(operator==="="?record.display_name===name:record.display_name?.includes(name)))){result.push(toIdDisplayName(record));}}
return result.slice(0,limit);}
onchange(ids,values,fieldNames,fieldsSpec){const kwargs=getKwArgs(arguments,"ids","values","field_names","fields_spec");({ids,values,field_names:fieldNames,fields_spec:fieldsSpec}=kwargs);fieldNames=ensureArray(fieldNames||[]);const firstOnChange=!fieldNames.length;const fieldsFromView=Object.keys(fieldsSpec);let serverValues={};const onchangeValues={};for(const fieldName in values){if(!(fieldName in this._fields)){throw makeServerError({type:"ValidationError",message:`Field ${fieldName} does not exist`,});}}
if(ids[0]){serverValues=this.read(ids,fieldsFromView,kwargs)[0];}else if(firstOnChange){fieldNames=fieldsFromView;for(const fieldName of fieldNames){if(!(fieldName in serverValues)&&fieldName!=="id"){onchangeValues[fieldName]=false;}}
const defaultValues=this.default_get(fieldsFromView,kwargs);for(const fieldName in defaultValues){if(isX2MField(this._fields[fieldName])){const subSpec=fieldsSpec[fieldName];for(const command of defaultValues[fieldName]){if(command[0]===0||command[0]===1){command[2]=pick(command[2],...Object.keys(subSpec.fields));}}}}
Object.assign(onchangeValues,defaultValues);}
const finalValues={...serverValues,...onchangeValues,...values};const proxy=new Proxy(finalValues,{set(target,p,newValue){if(target[p]!==newValue){onchangeValues[p]=newValue;}
return Reflect.set(target,p,newValue);},});for(const field of fieldNames){if(typeof this._onChanges[field]==="function"){this._onChanges[field](proxy);}}
return{value:convertToOnChange(this,onchangeValues,fieldsSpec),};}
read(idOrIds,fields,load){const kwargs=getKwArgs(arguments,"ids","fields","load");({ids:idOrIds,fields,load}=kwargs);const fieldNames=fields?.length?fields:Object.keys(this._fields);return this._read_format(idOrIds,fieldNames,load);}
read_group(domain,fields,groupby,offset,limit,orderby,lazy){const kwargs=getKwArgs(arguments,"domain","fields","groupby","offset","limit","orderby","lazy");({domain,fields,groupby,offset,limit,orderby,lazy=true}=kwargs);const records=this._filter(domain);let groupBy=[];if(groupby.length){groupBy=lazy?[groupby[0]]:groupby;}
const groupByFieldNames=groupBy.map((groupByField)=>safeSplit(groupByField,":")[0]);const aggregatedFields=[];if(fields.length===0){for(const fieldName in this._fields){if(!groupByFieldNames.includes(fieldName)){aggregatedFields.push([this._fields[fieldName],fieldName]);}}}else{for(const fspec of fields){const[,name,aggregator,fname]=fspec.match(R_AGGREGATE_FUNCTION);const fieldName=aggregator?fname||name:name;if(aggregator&&!VALID_AGGREGATE_FUNCTIONS.includes(aggregator)){throw new MockServerError(`Invalid aggregation function "${aggregator}"`);}
const field=this._fields[fieldName];if(!field||groupByFieldNames.includes(fieldName)||(["many2one","reference"].includes(field.type)&&!["count_distinct","array_agg"].includes(aggregator))){continue;}
aggregatedFields.push([field,name,aggregator]);}}
if(!groupBy.length){const group={__count:records.length,__domain:kwargs.domain};aggregateFields(aggregatedFields,group,records);return[group];}
const groups={};for(const record of records){let recordGroupValues=[];for(const gbField of groupBy){const[fieldName]=safeSplit(gbField,":");const value=formatFieldValue(this._fields,gbField,record[fieldName]);recordGroupValues=ensureArray(value).reduce((acc,val)=>{const newGroup={};newGroup[gbField]=val;if(recordGroupValues.length===0){acc.push(newGroup);}else{for(const groupValue of recordGroupValues){acc.push({...groupValue,...newGroup});}}
return acc;},[]);}
for(const groupValue of recordGroupValues){const valueKey=JSON.stringify(groupValue);groups[valueKey]=groups[valueKey]||[];groups[valueKey].push(record);}}
let readGroupResult=[];for(const[groupId,groupRecords]of Object.entries(groups)){const group={...JSON.parse(groupId),__domain:domain||[],__range:{},};for(const gbField of groupBy){if(!(gbField in group)){group[gbField]=false;continue;}
const[fieldName,granularity]=safeSplit(gbField,":");const value=Number.isInteger(group[gbField])?group[gbField]:group[gbField]||false;const{relation,type}=this._fields[fieldName];if(relation&&!Array.isArray(value)){const relatedRecord=this.env[relation].find(({id})=>id===value);if(relatedRecord){group[gbField]=[value,relatedRecord.display_name];}else{group[gbField]=false;}}
if(isDateField(type)){if(value){if(!READ_GROUP_NUMBER_GRANULARITY.includes(granularity)){let startDate,endDate;switch(granularity){case"hour":{startDate=parseDateTime(value,{format:"HH:00 dd MMM yyyy",});endDate=startDate.plus({hours:1});group[gbField]=startDate.toFormat("HH:00 dd MMM");break;}
case"day":{startDate=parseDateTime(value,{format:"yyyy-MM-dd"});endDate=startDate.plus({days:1});break;}
case"week":{startDate=parseDateTime(value,{format:"WW kkkk"});endDate=startDate.plus({weeks:1});break;}
case"quarter":{startDate=parseDateTime(value,{format:"q yyyy"});endDate=startDate.plus({quarters:1});break;}
case"year":{startDate=parseDateTime(value,{format:"y"});endDate=startDate.plus({years:1});break;}
case"month":default:{startDate=parseDateTime(value,{format:"MMMM yyyy"});endDate=startDate.plus({months:1});break;}}
const serialize=type==="date"?serializeDate:serializeDateTime;const from=serialize(startDate);const to=serialize(endDate);group.__range[gbField]={from,to};group.__domain=[[fieldName,">=",from],[fieldName,"<",to],...group.__domain,];}else{group.__domain=[[`${fieldName}.${granularity}`,"=",value],...group.__domain,];}}else{group.__range[gbField]=false;group.__domain=[[fieldName,"=",value],...group.__domain];}}else{group.__domain=[[fieldName,"=",value],...group.__domain];}}
if(Object.keys(group.__range||{}).length===0){delete group.__range;}
const groupByNoLeaf=kwargs.context?"group_by_no_leaf"in kwargs.context:false;let countKey;if(lazy&&(groupBy.length>=2||!groupByNoLeaf)){countKey=safeSplit(groupBy[0],":")[0]+"_count";}else{countKey="__count";}
group[countKey]=groupRecords.length;aggregateFields(aggregatedFields,group,groupRecords);readGroupResult.push(group);}
orderByField(this,orderby||groupByFieldNames.join(","),readGroupResult);if(limit){offset||=0;readGroupResult=readGroupResult.slice(offset,limit+offset);}
return readGroupResult;}
read_progress_bar(domain,groupBy,progressBar){const kwargs=getKwArgs(arguments,"domain","group_by","progress_bar");({domain,group_by:groupBy,progress_bar:progressBar}=kwargs);const groups=this.read_group(domain,[],[groupBy]);const data={};for(const group of groups){const records=this._filter(group.__domain);let groupByValue=group[groupBy];if(Array.isArray(groupByValue)){groupByValue=groupByValue[0];}
if(!(groupByValue in data)){if(groupByValue===true){groupByValue="True";}else if(groupByValue===false){groupByValue="False";}}
if(!(groupByValue in data)){data[groupByValue]={};for(const key in progressBar.colors){data[groupByValue][key]=0;}}
for(const record of records){const fieldValue=record[progressBar.field];if(fieldValue in data[groupByValue]){data[groupByValue][fieldValue]++;}}}
return data;}
render_public_asset(){return true;}
search(domain,offset,limit,order){const kwargs=getKwArgs(arguments,"domain","offset","limit","order");({domain,offset,limit,order}=kwargs);const{records}=this._search({context:kwargs.context,domain,limit,offset,order,});return records.map((record)=>record.id);}
search_count(domain,limit){const kwargs=getKwArgs(arguments,"domain","limit");({domain,limit}=kwargs);return this._search(kwargs).length;}
search_panel_select_range(fieldName){const kwargs=getKwArgs(arguments,"field_name");({field_name:fieldName}=kwargs);const field=this._fields[fieldName];const coModel=getRelation(field);const supportedTypes=["many2one","selection"];if(!supportedTypes.includes(field.type)){throw new MockServerError(`Only category types ${supportedTypes.join(" and ")} are supported, got "${
                    field.type
                }"`);}
const modelDomain=kwargs.search_domain||[];const extraDomain=new Domain([...(kwargs.category_domain||[]),...(kwargs.filter_domain||[]),]).toList();if(field.type==="selection"){kwargs.model_domain=modelDomain;return{parent_field:false,values:searchPanelSelectionRange(this,fieldName,{...kwargs,model_domain:modelDomain,extra_domain:extraDomain,}),};}
const fieldNames=["display_name"];let hierarchize="hierarchize"in kwargs?kwargs.hierarchize:true;let getParentId;let parentName=false;if(hierarchize&&coModel._fields[coModel._parent_name]){parentName=coModel._parent_name;fieldNames.push(parentName);getParentId=(record)=>record[parentName]?.[0]??false;}else{hierarchize=false;}
let comodelDomain=kwargs.comodel_domain||[];const enableCounters=kwargs.enable_counters;const expand=kwargs.expand;const limit=kwargs.limit;let domainImage;if(enableCounters||!expand){domainImage=searchPanelFieldImage(this,fieldName,{...kwargs,model_domain:modelDomain,extra_domain:extraDomain,only_counters:expand,set_limit:limit&&!(expand||hierarchize||comodelDomain),});}
if(!expand&&!hierarchize&&!comodelDomain.length){if(limit&&domainImage.size===limit){return{error_msg:"Too many items to display."};}
return{parent_field:parentName,values:[...domainImage.values()],};}
let imageElementIds;if(!expand){imageElementIds=[...domainImage.keys()].map(Number);let condition;if(hierarchize){const ancestorIds=new Set();for(const id of imageElementIds){let recordId=id;let record;while(recordId){ancestorIds.add(recordId);record=coModel.find((rec)=>rec.id===recordId);recordId=record[parentName];}}
condition=["id","in",unique(ancestorIds)];}else{condition=["id","in",imageElementIds];}
comodelDomain=new Domain([...comodelDomain,condition]).toList();}
let comodelRecords=coModel.search_read(comodelDomain,fieldNames,kwargs);if(hierarchize){const ids=expand?comodelRecords.map((rec)=>rec.id):imageElementIds;comodelRecords=searchPanelSanitizedParentHierarchy(comodelRecords,parentName,ids);}
if(limit&&comodelRecords.length===limit){return{error_msg:"Too many items to display."};}
const fieldRange=new Map();for(const record of comodelRecords){const values={id:record.id,display_name:record.display_name,};if(hierarchize){values[parentName]=getParentId(record);}
if(enableCounters){values.__count=domainImage.get(record.id)?domainImage.get(record.id).__count:0;}
fieldRange.set(record.id,values);}
if(hierarchize&&enableCounters){searchPanelGlobalCounters(fieldRange,parentName);}
return{parent_field:parentName,values:[...fieldRange.values()],};}
search_panel_select_multi_range(fieldName,groupBy){const kwargs=getKwArgs(arguments,"field_name","group_by");({field_name:fieldName,group_by:groupBy}=kwargs);const field=this._fields[fieldName];const coModel=getRelation(field);const supportedTypes=["many2many","many2one","selection"];if(!supportedTypes.includes(field.type)){throw new MockServerError(`Only filter types ${supportedTypes} are supported, got "${field.type}"`);}
let modelDomain=kwargs.search_domain||[];let extraDomain=new Domain([...(kwargs.category_domain||[]),...(kwargs.filter_domain||[]),]).toList();if(field.type==="selection"){return{values:searchPanelSelectionRange(this,fieldName,{...kwargs,model_domain:modelDomain,extra_domain:extraDomain,}),};}
const fieldNames=["display_name"];let groupIdName;if(groupBy){const groupByField=coModel._fields[groupBy];fieldNames.push(groupBy);if(isM2OField(groupByField)){groupIdName=(value)=>value||[false,"Not set"];}else if(groupByField.type==="selection"){const groupBySelection={...coModel._fields[groupBy].selection,[false]:"Not Set",};groupIdName=(value)=>[value,groupBySelection[value]];}else{groupIdName=(value)=>(value?[value,value]:[false,"Not set"]);}}
let comodelDomain=kwargs.comodel_domain||[];const enableCounters=kwargs.enable_counters;const expand=kwargs.expand;const limit=kwargs.limit;if(isX2MField(field)){const comodelRecords=coModel.search_read(comodelDomain,fieldNames,kwargs);if(expand&&limit&&comodelRecords.length===limit){return{error_msg:"Too many items to display."};}
const groupDomain=kwargs.group_domain;const fieldRange=[];for(const record of comodelRecords){const values={id:record.id,display_name:record.display_name,};let groupId;if(groupBy){const[gId,gName]=groupIdName(record[groupBy]);values.group_id=groupId=gId;values.group_name=gName;}
let count;let inImage;if(enableCounters||!expand){const searchDomain=new Domain([...modelDomain,[fieldName,"in",record.id],]).toList();let localExtraDomain=extraDomain;if(groupBy&&groupDomain){localExtraDomain=new Domain([...localExtraDomain,...(groupDomain[JSON.stringify(groupId)]||[]),]).toList();}
const searchCountDomain=new Domain([...searchDomain,...localExtraDomain,]).toList();if(enableCounters){count=this.search_count(searchCountDomain);}
if(!expand){if(enableCounters&&JSON.stringify(localExtraDomain)==="[]"){inImage=count;}else{inImage=this.search(searchDomain,[],1).length;}}}
if(expand||inImage){if(enableCounters){values.__count=count;}
fieldRange.push(values);}}
if(!expand&&limit&&fieldRange.length===limit){return{error_msg:"Too many items to display."};}
return{values:fieldRange};}
if(isM2OField(field)){let domainImage;if(enableCounters||!expand){extraDomain=new Domain([...extraDomain,...(kwargs.group_domain||[])]).toList();modelDomain=new Domain([...modelDomain,...(kwargs.group_domain||[])]).toList();domainImage=searchPanelFieldImage(this,fieldName,{...kwargs,model_domain:modelDomain,extra_domain:extraDomain,only_counters:expand,set_limit:limit&&!(expand||groupBy||comodelDomain),});}
if(!expand&&!groupBy&&!comodelDomain.length){if(limit&&domainImage.size===limit){return{error_msg:"Too many items to display."};}
return{values:[...domainImage.values()]};}
if(!expand){const imageElementIds=[...domainImage.keys()].map(Number);comodelDomain=new Domain([...comodelDomain,["id","in",imageElementIds],]).toList();}
const comodelRecords=coModel.search_read(comodelDomain,fieldNames,kwargs);if(limit&&comodelRecords.length===limit){return{error_msg:"Too many items to display."};}
const fieldRange=[];for(const record of comodelRecords){const values={id:record.id,display_name:record.display_name,};if(groupBy){const[groupId,groupName]=groupIdName(record[groupBy]);values.group_id=groupId;values.group_name=groupName;}
if(enableCounters){values.__count=domainImage.get(record.id)?domainImage.get(record.id).__count:0;}
fieldRange.push(values);}
return{values:fieldRange};}}
search_read(domain,fields,offset,limit,order){const kwargs=getKwArgs(arguments,"domain","fields","offset","limit","order");({domain,fields,offset,limit,order}=kwargs);if(!fields?.length){fields=Object.keys(this._fields);}
const{records}=this._search({context:kwargs.context,domain,limit,offset,order,});return this.read(records.map((r)=>r.id),unique([...fields,"id"]));}
unlink(idOrIds){const kwargs=getKwArgs(arguments,"ids");({ids:idOrIds}=kwargs);const ids=ensureArray(idOrIds);for(let i=this.length-1;i>=0;i--){if(ids.includes(this[i].id)){this.splice(i,1);}}
for(const model of Object.values(MockServer.current._models)){for(const[fieldName,field]of Object.entries(model._fields)){const coModel=getRelation(field);if(coModel?._name===this._name){for(const record of model){if(Array.isArray(record[fieldName])){record[fieldName]=record[fieldName].filter((id)=>!ids.includes(id));}else if(ids.includes(record[fieldName])){record[fieldName]=false;}}}}}
return true;}
web_read(idOrIds,specification){const kwargs=getKwArgs(arguments,"ids","specification");({ids:idOrIds,specification}=kwargs);const ids=ensureArray(idOrIds);let fieldNames=Object.keys(specification);if(!fieldNames.length){fieldNames=["id"];}
const records=this.read(ids,fieldNames,kwargs);this._unityReadRecords(records,specification);return records;}
web_read_group(domain,fields,groupby,limit,offset,orderby,lazy){const kwargs=getKwArgs(arguments,"domain","fields","groupby","limit","offset","orderby","lazy");({domain,fields,groupby,limit,offset,orderby,lazy}=kwargs);const groups=this.read_group(kwargs);const allGroups=this.read_group(domain,["display_name"],groupby,makeKwArgs({lazy}));return{groups,length:allGroups.length};}
web_save(idOrIds,values,specification,nextId){const kwargs=getKwArgs(arguments,"ids","vals","specification","next_id");({ids:idOrIds,vals:values,specification,next_id:nextId}=kwargs);let ids=ensureArray(idOrIds);if(ids.length===0){ids=this.create([values],kwargs);}else{this.write(ids,values);}
if(nextId){ids=nextId;}
return this.web_read(ids,specification);}
web_search_read(domain,specification,offset,limit,order,countLimit){const kwargs=getKwArgs(arguments,"domain","specification","offset","limit","order","count_limit");({domain,specification,offset,limit,order,count_limit:countLimit}=kwargs);const fieldNames=Object.keys(specification);const{length,records}=this._search({context:kwargs.context,domain,limit,offset,order,});const result={length,records:this.read(records.map((r)=>r.id),unique(["id",...fieldNames])),};if(countLimit){result.length=Math.min(result.length,countLimit);}
this._unityReadRecords(result.records,specification);return result;}
with_user(user){return this;}
write(idOrIds,values){const kwargs=getKwArgs(arguments,"ids","vals");({ids:idOrIds,vals:values}=kwargs);const ids=ensureArray(idOrIds);const originalRecords={};for(const id of ids){originalRecords[id]={...this.browse(id)[0]};this._write(values,id);}
this.browse(ids)._applyComputesAndValidate(originalRecords);return true;}
_applyComputesAndValidate(originalRecords={}){for(const fieldName of this._related){this._compute_related_field(fieldName);}
for(const computeFn of Object.values(this._computes)){computeFn.call(this);}
for(const record of this){for(const fieldName of Object.keys(record)){const fieldDef=this._fields[fieldName];if(!isValidFieldValue(record,fieldDef)){throw new MockServerError(`Invalid value for field "${fieldName}" on ${getRecordQualifier(
                            record
                        )} in model "${this._name}": expected "${fieldDef.type}" and got: ${
                            record[fieldName]
                        }`);}}
updateComodelRelationalFields(this,record,originalRecords[record.id]);}}
_applyDefaults(record,context){for(const fieldName in this._fields){if(fieldName==="id"||record[fieldName]!==undefined){continue;}
if(fieldName==="active"){record[fieldName]=true;continue;}
if(fieldName==="create_uid"){if("res.users"in MockServer.current._models){record[fieldName]=this.env.uid;}
continue;}
const fieldDef=this._fields[fieldName];if(context&&`default_${fieldName}`in context){record[fieldName]=context[`default_${fieldName}`];}else if("default"in fieldDef){record[fieldName]=typeof fieldDef.default==="function"?fieldDef.default.call(this,record):fieldDef.default;}else if(fieldDef.type in DEFAULT_FIELD_VALUES){record[fieldName]=DEFAULT_FIELD_VALUES[fieldDef.type]();}}}
_compute_display_name(){if(this._rec_name){for(const record of this){const value=record[this._rec_name];record.display_name=(value&&String(value))??false;}}else{for(const record of this){record.display_name=`${this._name},${record.id}`;}}}
_compute_related_field(fieldName){const field=this._fields[fieldName];const fieldNames=safeSplit(field.related,".");for(const record of this){const[value,fieldType]=this._followRelation(record,fieldNames);if(!fieldType){this.env[this._name]._related.delete(fieldName);return;}
if(value===undefined){record[fieldName]??=DEFAULT_FIELD_VALUES[fieldType]();}else{record[fieldName]=value;}}}
_filter(domain,options){domain||=[];if(!Array.isArray(domain)){throw new TypeError(`domain must be an array, got: ${domain}`);}
const activeTest=(options?.active_test??true)&&this._fields.active;if(domain.length===1){const[[fieldName,operator,value]]=domain;let simpleFilter;switch(typeof fieldName){case"boolean":case"number":{let shouldBeIncluded;if(domain[0].length===1){shouldBeIncluded=Boolean(fieldName);}else{shouldBeIncluded=fieldName===value;if(operator==="!="){shouldBeIncluded=!shouldBeIncluded;}}
if(activeTest){simpleFilter=()=>shouldBeIncluded;}else{return shouldBeIncluded?this:new this.constructor();}
break;}
case"string":{if(fieldName==="id"&&("in","=").includes(operator)){const values=ensureArray(value);simpleFilter=(record)=>values.includes(record[fieldName]);}
break;}}
if(simpleFilter){return this.filter((record)=>simpleFilter(record)&&(!activeTest||record.active));}}
if(activeTest){const activeInDomain=domain.some((subDomain)=>subDomain[0]==="active");if(!activeInDomain){domain=[...domain,["active","=",true]];}}
if(!domain.length){return this;}
domain=domain.map((criterion)=>{if(criterion[1]==="child_of"){let oldLength=0;const childIds=[criterion[2]];while(childIds.length>oldLength){oldLength=childIds.length;for(const record of this){if(childIds.indexOf(record[this._parent_name])>=0){childIds.push(record.id);}}}
criterion=[criterion[0],"in",childIds];}
const field=this._fields[criterion[0]]||{};if(isX2MField(field)&&criterion[1]==="="){if(criterion[2]===false){criterion=[criterion[0],"=",[]];}else{criterion=[criterion[0],"in",[criterion[2]]];}}
return criterion;});const filterDomain=new Domain(domain);return this.filter((record)=>filterDomain.contains(record));}
_followRelation(record,fieldNames){let currentModel=this;let currentRecord=record;let currentField;let value;for(const fieldName of fieldNames){currentField=currentModel._fields[fieldName];if(!currentField){break;}
if(!currentRecord){value=undefined;break;}
value=currentRecord?.[fieldName];const relation=getRelation(currentField,currentRecord);if(relation){const ids=ensureArray(currentRecord?.[fieldName]);currentModel=relation;currentRecord=currentModel.find((r)=>ids.includes(r.id));}}
return[value,currentField?.type];}
_getNextId(){return Math.max(0,...this.map((record)=>record?.id||0))+1;}
_getPropertyContainer(field,record){const relationField=this._fields[field.definition_record];if(relationField){const containerModel=getRelation(this._fields[field.definition_record]);const containerId=record[field.definition_record];if(containerId){return containerModel.browse(containerId)[0];}}
return null;}
_read_format(idOrIds,fnames=[],load="_classic_read"){const ids=ensureArray(idOrIds);const fieldNames=unique(["id",...fnames]);const records=[];const validFields=[];const modelMap={[this._name]:{},};for(const record of this){modelMap[this._name][record.id]=record;}
for(const fieldName of fieldNames){const field=this._fields[fieldName];if(field){validFields.push(field);}else{continue;}
if(field.type==="many2one_reference"){for(const record of this){const coModel=getRelation(field,record);if(!coModel){continue;}
modelMap[coModel._name]||={};modelMap[coModel._name][record[fieldName]]=record[fieldName];}}else if(isM2OField(field.type)){const coModel=getRelation(field);if(coModel&&!modelMap[coModel._name]){modelMap[coModel._name]={};for(const record of coModel){modelMap[coModel._name][record.id]=record;}}}}
for(const id of ids){if(!id){throw new MockServerError(`Cannot read: falsy ID value would result in an access error on the actual server`);}
const record=modelMap[this._name][id];if(!record){continue;}
const result={id:record.id};for(const field of validFields){if(["float","integer","monetary"].includes(field.type)){result[field.name]=record[field.name]||0;}else if(isM2OField(field)){const coModel=getRelation(field,record);const relRecord=coModel&&modelMap[coModel._name][record[field.name]];if(relRecord){if(field.type==="many2one_reference"||load!=="_classic_read"){result[field.name]=record[field.name];}else{result[field.name]=[record[field.name],relRecord.display_name];}}else{result[field.name]=false;}}else if(isX2MField(field)){result[field.name]=record[field.name]||[];}else if(field.type==="properties"){const container=this._getPropertyContainer(field,record);if(container){result[field.name]=container[field.definition_record_field].map((def)=>({...def,value:record[field.name][def.name]??false,}));}else{result[field.name]=false;}}else{result[field.name]=record[field.name]??false;}}
records.push(result);}
return records;}
_search(params){const offset=params.offset||0;const records=this._filter(params.domain,{active_test:params.context?.active_test,});orderByField(records,params.order);const endLimit=params.limit?offset+params.limit:undefined;return{length:records.length,records:records.slice(offset,endLimit),};}
_unityReadRecords(records,spec){for(const fieldName in spec){const field=this._fields[fieldName];const relatedFields=spec[fieldName].fields;switch(field.type){case"reference":{for(const record of records){if(!record[fieldName]){continue;}
const[modelName,id]=getReferenceValue(record[fieldName]);record[fieldName]={};if(relatedFields&&Object.keys(relatedFields).length){const result=this.env[modelName].web_read(id,relatedFields,makeKwArgs({context:spec[fieldName].context}));record[fieldName]=result[0];}
record[fieldName].id={id,model:modelName};}
break;}
case"many2one_reference":{for(const record of records){const id=record[fieldName];if(!id){record[fieldName]=0;continue;}
if(!relatedFields){continue;}
const dbRecord=this.find((r)=>r.id===record.id);const model=dbRecord[field.model_field];record[fieldName]={};if(relatedFields&&Object.keys(relatedFields).length){const[result]=this.env[model].web_read(id,relatedFields,makeKwArgs({context:spec[fieldName].context}));record[fieldName]=result;}}
break;}
case"many2many":case"one2many":{if(relatedFields&&Object.keys(relatedFields).length){const{limit,order}=spec[fieldName];const relModel=getRelation(field);for(const record of records){let relResIds=record[fieldName];if(order){const relRecords=relModel.read(relResIds);const orderedRelRecords=orderByField(relModel,order,relRecords);relResIds=orderedRelRecords.map((r)=>r.id);}
let result=relModel.web_read(relResIds,relatedFields,makeKwArgs({context:spec[fieldName].context}));if(limit){result=result.map((r,i)=>(i<limit?r:{id:r.id}));}
record[fieldName]=result;}}
break;}
case"many2one":{for(const record of records){if(record[fieldName]!==false){if(!relatedFields){record[fieldName]=record[fieldName][0];}else{record[fieldName]=getRelation(field).web_read([record[fieldName][0]],relatedFields,makeKwArgs({context:spec[fieldName].context}))[0];}}}
break;}}}}
_write(values,id){const record=this.find((r)=>r.id===id);const todoValsMap=new Map(Object.entries(values));const MAX_ITER=todoValsMap.size;let i=0;while(todoValsMap.size>0&&i<MAX_ITER){let[fieldName,value]=todoValsMap.entries().next().value;todoValsMap.delete(fieldName);const field=this._fields[fieldName];if(!field){throw fieldNotFoundError(this._name,fieldName,`could not write on ${getRecordQualifier(record)}`);}
if(isX2MField(field)){let ids=record[fieldName]?record[fieldName].slice():[];if(Array.isArray(value)&&value.length){if(value.reduce((hasOnlyInt,val)=>hasOnlyInt&&Number.isInteger(val),true)){value=[[6,0,value]];}}else if(value===false){value=[[5]];}
for(const command of value||[]){const coModel=getRelation(field,record);if(command[0]===0){const inverseData=command[2];const inverseFieldName=field.inverse_fname_by_model_name?.[coModel._name];if(inverseFieldName){inverseData[inverseFieldName]=field.type==="many2many"?[id]:id;}
const[newId]=coModel.create([inverseData]);ids.push(newId);}else if(command[0]===1){coModel.write([command[1]],command[2]);}else if(command[0]===2||command[0]===3){ids.splice(ids.indexOf(command[1]),1);}else if(command[0]===4){if(!ids.includes(command[1])){ids.push(command[1]);}}else if(command[0]===5){ids=[];}else if(command[0]===6){ids=[...command[2]];}else{throw new MockServerError(`Command "${JSON.stringify(
                                value
                            )}" is not supported by the MockServer on field "${fieldName}" in model "${
                                this._name
                            }"`);}}
record[fieldName]=ids;}else if(isM2OField(field)){if(value){if(!isValidId(value,field,record)){if(todoValsMap.has(field.model_name_ref_fname)){todoValsMap.set(fieldName,value);continue;}
throw new MockServerError(`Invalid ID "${JSON.stringify(
                                value
                            )}" for a many2one on field "${fieldName}" in model "${this._name}"`);}
record[fieldName]=value;}else{record[fieldName]=false;}}else if(field.type==="properties"){const properties=value||[];if(properties.some((p)=>p.definition_changed||p.definition_deleted)){const container=this._getPropertyContainer(field,record);container[field.definition_record_field]=[];for(const property of properties){const definition={...property};delete definition.definition_changed;delete definition.definition_deleted;delete definition.value;if(!property.definition_deleted){container[field.definition_record_field].push(definition);}}}
record[fieldName]||={};for(const property of properties){if(property.definition_deleted){delete record[fieldName][property.name];}else{let value=property.value??property.default??false;if(value&&property.comodel){const coModel=this.env[property.comodel];switch(property.type){case"one2many":case"many2many":{value=coModel.browse(value).map(toIdDisplayName);break;}
case"many2one":{value=toIdDisplayName(coModel.browse(value[0])[0]);break;}}}
record[fieldName][property.name]=value;}}}else if(!isComputed(field)){record[fieldName]=value;}
i++;}}}
const ServerModel=__exports.ServerModel=class ServerModel extends Model{static _fetch=true;}
const Command=__exports.Command={clear:()=>[5],create:(values)=>[0,0,values],delete:(id)=>[2,id],link:(id)=>[4,id],set:(ids)=>[6,0,ids],unlink:(id)=>[3,id],update:(id,values)=>[1,id,values],};return __exports;});;

/* /web/static/tests/_framework/mock_server/mock_models/ir_attachment.js */
odoo.define('@web/../tests/_framework/mock_server/mock_models/ir_attachment',['@web/../tests/_framework/mock_server/mock_model'],function(require){'use strict';let __exports={};const{ServerModel}=require("@web/../tests/_framework/mock_server/mock_model");const IrAttachment=__exports.IrAttachment=class IrAttachment extends ServerModel{_name="ir.attachment";}
return __exports;});;

/* /web/static/tests/_framework/mock_server/mock_models/ir_model.js */
odoo.define('@web/../tests/_framework/mock_server/mock_models/ir_model',['@web/../tests/_framework/mock_server/mock_model'],function(require){'use strict';let __exports={};const{ServerModel}=require("@web/../tests/_framework/mock_server/mock_model");const IrModel=__exports.IrModel=class IrModel extends ServerModel{_name="ir.model";_records=[{id:1,model:"res.partner",name:"Partner",},];}
return __exports;});;

/* /web/static/tests/_framework/mock_server/mock_models/ir_model_access.js */
odoo.define('@web/../tests/_framework/mock_server/mock_models/ir_model_access',['@web/../tests/_framework/mock_server/mock_model'],function(require){'use strict';let __exports={};const{Model}=require("@web/../tests/_framework/mock_server/mock_model");const IrModelAccess=__exports.IrModelAccess=class IrModelAccess extends Model{_name="ir.model.access";has_access(){return true;}}
return __exports;});;

/* /web/static/tests/_framework/mock_server/mock_models/ir_model_fields.js */
odoo.define('@web/../tests/_framework/mock_server/mock_models/ir_model_fields',['@web/../tests/_framework/mock_server/mock_model'],function(require){'use strict';let __exports={};const{ServerModel}=require("@web/../tests/_framework/mock_server/mock_model");const IrModelFields=__exports.IrModelFields=class IrModelFields extends ServerModel{_name="ir.model.fields";}
return __exports;});;

/* /web/static/tests/_framework/mock_server/mock_models/ir_rule.js */
odoo.define('@web/../tests/_framework/mock_server/mock_models/ir_rule',['@web/../tests/_framework/mock_server/mock_model'],function(require){'use strict';let __exports={};const{Model}=require("@web/../tests/_framework/mock_server/mock_model");const IrRule=__exports.IrRule=class IrRule extends Model{_name="ir.rule";has_access(){return true;}}
return __exports;});;

/* /web/static/tests/_framework/mock_server/mock_models/ir_ui_view.js */
odoo.define('@web/../tests/_framework/mock_server/mock_models/ir_ui_view',['@web/../tests/_framework/mock_server/mock_model'],function(require){'use strict';let __exports={};const{ServerModel}=require("@web/../tests/_framework/mock_server/mock_model");const IrUiView=__exports.IrUiView=class IrUiView extends ServerModel{_name="ir.ui.view";has_access(){return true;}}
return __exports;});;

/* /web/static/tests/_framework/mock_server/mock_models/res_company.js */
odoo.define('@web/../tests/_framework/mock_server/mock_models/res_company',['@web/../tests/_framework/mock_server_state.hoot','@web/../tests/_framework/mock_server/mock_fields','@web/../tests/_framework/mock_server/mock_model'],function(require){'use strict';let __exports={};const{serverState}=require("@web/../tests/_framework/mock_server_state.hoot");const fields=require("@web/../tests/_framework/mock_server/mock_fields");const{ServerModel}=require("@web/../tests/_framework/mock_server/mock_model");const ResCompany=__exports.ResCompany=class ResCompany extends ServerModel{_name="res.company";description=fields.Text();is_company=fields.Boolean({default:false});_records=serverState.companies.map((company)=>({id:company.id,active:true,name:company.name,partner_id:company.id,}));}
return __exports;});;

/* /web/static/tests/_framework/mock_server/mock_models/res_country.js */
odoo.define('@web/../tests/_framework/mock_server/mock_models/res_country',['@web/../tests/_framework/mock_server/mock_model'],function(require){'use strict';let __exports={};const{ServerModel}=require("@web/../tests/_framework/mock_server/mock_model");const ResCountry=__exports.ResCountry=class ResCountry extends ServerModel{_name="res.country";}
return __exports;});;

/* /web/static/tests/_framework/mock_server/mock_models/res_currency.js */
odoo.define('@web/../tests/_framework/mock_server/mock_models/res_currency',['@web/../tests/_framework/mock_server_state.hoot','@web/../tests/_framework/mock_server/mock_model'],function(require){'use strict';let __exports={};const{serverState}=require("@web/../tests/_framework/mock_server_state.hoot");const{ServerModel}=require("@web/../tests/_framework/mock_server/mock_model");const ResCurrency=__exports.ResCurrency=class ResCurrency extends ServerModel{_name="res.currency";_records=Object.entries(serverState.currencies).map(([id,{digits,name,position,symbol}])=>({id:Number(id)+1,decimal_places:digits?.at(-1)??2,name,position,symbol,}));}
return __exports;});;

/* /web/static/tests/_framework/mock_server/mock_models/res_groups.js */
odoo.define('@web/../tests/_framework/mock_server/mock_models/res_groups',['@web/../tests/_framework/mock_server_state.hoot','@web/../tests/_framework/mock_server/mock_model'],function(require){'use strict';let __exports={};const{serverState}=require("@web/../tests/_framework/mock_server_state.hoot");const{ServerModel}=require("@web/../tests/_framework/mock_server/mock_model");const ResGroups=__exports.ResGroups=class ResGroups extends ServerModel{_name="res.groups";_records=[{id:serverState.groupId,name:"Internal User",},];}
return __exports;});;

/* /web/static/tests/_framework/mock_server/mock_models/res_partner.js */
odoo.define('@web/../tests/_framework/mock_server/mock_models/res_partner',['@web/../tests/_framework/mock_server_state.hoot','@web/../tests/_framework/mock_server/mock_model'],function(require){'use strict';let __exports={};const{serverState}=require("@web/../tests/_framework/mock_server_state.hoot");const{ServerModel}=require("@web/../tests/_framework/mock_server/mock_model");const ResPartner=__exports.ResPartner=class ResPartner extends ServerModel{_name="res.partner";_records=[...serverState.companies.map((company)=>({id:company.id,active:true,name:company.name,})),{id:serverState.partnerId,active:true,name:serverState.partnerName,},{id:serverState.publicPartnerId,active:true,is_public:true,name:serverState.publicPartnerName,},{id:serverState.odoobotId,active:false,im_status:"bot",name:"OdooBot",},];}
return __exports;});;

/* /web/static/tests/_framework/mock_server/mock_models/res_users.js */
odoo.define('@web/../tests/_framework/mock_server/mock_models/res_users',['@web/../tests/_framework/mock_server_state.hoot','@web/../tests/_framework/mock_server/mock_model'],function(require){'use strict';let __exports={};const{serverState}=require("@web/../tests/_framework/mock_server_state.hoot");const{ServerModel}=require("@web/../tests/_framework/mock_server/mock_model");const ResUsers=__exports.ResUsers=class ResUsers extends ServerModel{_name="res.users";_records=[{id:serverState.userId,active:true,company_id:serverState.companies[0]?.id,company_ids:serverState.companies.map((company)=>company.id),login:"admin",partner_id:serverState.partnerId,password:"admin",},{id:serverState.publicUserId,active:false,login:"public",partner_id:serverState.publicPartnerId,password:"public",},];has_group(){return true;}
_is_public(id){return id===serverState.publicUserId;}
create(){const userId=super.create(...arguments);const[user]=this.env["res.users"].browse(userId);if(user&&!user.partner_id){this.env["res.users"].write(userId,{partner_id:this.env["res.partner"].create({})});}
return userId;}}
return __exports;});;

/* /web/static/tests/_framework/mock_server/mock_server.js */
odoo.define('@web/../tests/_framework/mock_server/mock_server',['@odoo/hoot','@web/core/network/rpc','@web/core/registry','@web/core/utils/arrays','@web/core/utils/objects','@web/../tests/_framework/mock_server_state.hoot','@web/../tests/_framework/module_set.hoot','@web/../tests/_framework/mock_server/mock_fields','@web/../tests/_framework/mock_server/mock_server_utils'],function(require){'use strict';let __exports={};const{before,createJobScopedGetter,expect,getCurrent,mockFetch,mockLocation,mockWebSocket,registerDebugInfo,}=require("@odoo/hoot");const{makeErrorFromResponse,RPCError}=require("@web/core/network/rpc");const{registry}=require("@web/core/registry");const{ensureArray,isIterable}=require("@web/core/utils/arrays");const{isObject}=require("@web/core/utils/objects");const{serverState}=require("@web/../tests/_framework/mock_server_state.hoot");const{fetchModelDefinitions,globalCachedFetch,registerModelToFetch}=require("@web/../tests/_framework/module_set.hoot");const{DEFAULT_FIELD_PROPERTIES,getFieldDisplayName,S_SERVER_FIELD}=require("@web/../tests/_framework/mock_server/mock_fields");const{getRecordQualifier,makeKwArgs,makeServerError,MockServerError,safeSplit,}=require("@web/../tests/_framework/mock_server/mock_server_utils");const{DateTime}=luxon;function authenticateUser(user){const{env}=MockServer;if(!user?.id){throw new MockServerError("Unauthorized");}
env.cookie.set("sid",user.id);env.uid=user.id;}
function deepCopy(object){if(!object){return object;}
if(typeof object==="object"){if(object?.nodeType){return object.cloneNode(true);}else if(object instanceof Date||object instanceof DateTime){return new object.constructor(object);}else if(isIterable(object)){const copy=[...object].map(deepCopy);if(object instanceof Set||object instanceof Map){return new object.constructor(copy);}else{return copy;}}else{return Object.fromEntries(Object.entries(object).map(([key,object])=>[key,deepCopy(object)]));}}
return object;}
function getAssignAction(options){const shouldAdd=options?.mode==="add";return function assign(target,key,value){if(shouldAdd&&isObject(target[key])){if(Array.isArray(target[key])){target[key].push(...value);}else{Object.assign(target[key],value);}}else{target[key]=value;}};}
function getCurrentMockServer(){const{test}=getCurrent();if(!test||!test.run){return null;}
if(!mockServers.has(test.run)){mockServers.set(test.run,new MockServer());}
return mockServers.get(test.run);}
function getJsonRpcParams({headers,body}){if(headers.get("Content-Type")!=="application/json"||typeof body!=="string"){return null;}
try{const parsedParams=JSON.parse(body);return{id:parsedParams.id,jsonrpc:parsedParams.jsonrpc,};}catch{return{id:nextJsonRpcId++,jsonrpc:"2.0",};}}
function makeServerEnv(models){const serverEnv=new MockServerBaseEnvironment();return new Proxy(serverEnv,{get:(target,p)=>{if(p in target||typeof p!=="string"||p==="then"){return Reflect.get(target,p);}
const model=Reflect.get(models,p);if(!model){throw modelNotFoundError(p,"could not get model from server environment");}
return model;},has:(target,p)=>Reflect.has(target,p)||Reflect.has(models,p),});}
function match(target,matchers){return matchers.some((matcher)=>matcher==="*"||(matcher instanceof RegExp?matcher.test(target):target===matcher));}
function modelNotFoundError(modelName,consequence){return new MockServerError(`Cannot find a definition for model "${modelName}": ${consequence} (did you forget to use \`defineModels()?\`)`);}
function parseTranslations(translations){return isIterable(translations)?translations:Object.entries(translations).map(([id,string])=>({id,string}));}
function _defineParams(params,options){const assign=getAssignAction(options);const currentParams=getCurrentParams();for(const[key,value]of Object.entries(params)){assign(currentParams,key,value);}
return MockServer.current?.configure(params);}
const getCurrentParams=createJobScopedGetter(function getCurrentParams(previous){return{...previous,actions:deepCopy(previous?.actions||[]),menus:deepCopy(previous?.menus||[DEFAULT_MENU]),models:[...(previous?.models||[])],routes:[...(previous?.routes||[])],};});class MockServerBaseEnvironment{cookie=new Map();get companies(){return MockServer.env["res.company"].read(serverState.companies.map((c)=>c.id));}
get company(){return this.companies[0];}
get context(){return{lang:serverState.lang,tz:serverState.timezone,uid:serverState.userId,};}
get lang(){return serverState.lang;}
get uid(){return serverState.userId;}
set uid(newUid){serverState.userId=newUid;const user=this.user;if(user){serverState.partnerId=user.partner_id;}}
get user(){return MockServer.env["res.users"].browse(serverState.userId)[0];}}
const ACTION_IDENTIFIERS=["id","xml_id","path"];const ACTION_TYPES={actions:"ir.actions.actions",client:"ir.actions.client",close:"ir.actions.act_window_close",embedded:"ir.embedded.actions",report:"ir.actions.report",server:"ir.actions.server",todo:"ir.actions.todo",url:"ir.actions.act_url",view:"ir.actions.act_window.view",window:"ir.actions.act_window",};const ALLOWED_CHARS={default:"[^/]",int:"\\d",path:".",string:"[\\w:.-]",};const DEFAULT_MENU={id:1,appID:1,name:"App1",};const ROOT_MENU={id:"root",name:"root",appID:"root",};const R_DATASET_ROUTE=/\/web\/dataset\/call_(?:button|kw)\/[\w.-]+\/(?<step>\w+)/;const R_URL_SPECIAL_CHARACTERS=/[.$+()]/g;const R_ROUTE_PARAM=/<(?:(?<type>\w+):)?(?<name>[\w-]+)>/g;const R_WILDCARD=/\*+/g;const R_WEBCLIENT_ROUTE=/(?<step>\/web\/webclient\/\w+)/;const mockServers=new WeakMap();const seenModels=new WeakSet();let nextJsonRpcId=1e9;const MockServer=__exports.MockServer=class MockServer{static get current(){const mockServer=getCurrentMockServer();return mockServer?._started?mockServer:null;}
static get env(){return this.current?.env;}
static get state(){return serverState;}
actions=[];menus=[];_lang_parameters={date_format:"%m/%d/%Y",decimal_point:".",direction:"ltr",grouping:[3,0],time_format:"%H:%M:%S",short_time_format:"%H:%M",thousands_sep:",",week_start:7,};_models=Object.create(null);_modelSpecs=[];_modelNamesToFetch=new Set();_modules={web:{messages:[]},};_ormListeners=[];_routes=[];_started=false;_websockets=[];env=makeServerEnv(this._models);async configure(params,options){const assign=getAssignAction(options);if(params.actions){assign(this,"actions",params.actions);}
if(params.lang){assign(serverState,"lang",params.lang);}
if(params.lang_parameters){Object.assign(this._lang_parameters,params.lang_parameters);}
if(params.menus){assign(this,"menus",params.menus);}
if(params.models){assign(this,"_modelSpecs",[...params.models].map((ModelClass)=>this._getModelDefinition(ModelClass)));if(this._started){await this._loadModels();}}
if(params.modules){for(const[module,values]in Object.entries(params.modules)){this._modules[module]||={messages:[]};assign(this._modules[module],"messages",parseTranslations(values.message||values));}}
if(params.multi_lang){assign(serverState,"multiLang",params.multi_lang);}
if(params.timezone){assign(serverState,"timezone",params.timezone);}
if(params.translations){assign(this._modules.web,"messages",parseTranslations(params.translations));}
if(params.routes){for(const args of params.routes){this._onRpc(...args);}}
return this;}
getWebSockets(url){return url?this._websockets.filter((ws)=>ws.url.includes(url)):this._websockets;}
async start(){if(this._started){throw new MockServerError("MockServer has already been _started");}
this._started=true;registerDebugInfo("mock server",this);mockFetch(this._handleRequest.bind(this));mockWebSocket(this._handleWebSocket.bind(this));this._onRoute(["/web/action/load"],this.loadAction);this._onRoute(["/web/action/load_breadcrumbs"],this.loadActionBreadcrumbs);this._onRoute(["/web/bundle/<string:bundle_name>"],this.loadBundle);this._onRoute(["/web/dataset/call_kw","/web/dataset/call_kw/<path:path>","/web/dataset/call_button","/web/dataset/call_button/<path:path>",],this.callKw,{final:true});this._onRoute(["/web/dataset/resequence"],this.resequence);this._onRoute(["/web/image/<string:model>/<int:id>/<string:field>"],this.loadImage);this._onRoute(["/web/webclient/load_menus/<string:unique>"],this.loadMenus);this._onRoute(["/web/webclient/translations/<string:unique>"],this.loadTranslations);const mockRpcEntries=registry.category("mock_rpc").getEntries();if(mockRpcEntries.length){console.warn("Warning: 'mock_rpc' registry is deprecated; use 'onRpc' with the same parameters instead.");for(const[route,callback]of mockRpcEntries){if(typeof callback==="function"){this._onRpc(route,callback);}}}
await this.configure(getCurrentParams());return this;}
_callOrm(params){const{args,method,model:modelName,kwargs}=params;if(modelName){const model=this.env[modelName];if(typeof model[method]==="function"){const expectedLength=model[method].length;while(args.length<expectedLength){args.push(undefined);}
return model[method](...args,kwargs);}
for(const parentName of safeSplit(model._inherit)){const parentModel=this.env[parentName];if(typeof parentModel[method]==="function"){const expectedLength=parentModel[method].length;while(args.length<expectedLength){args.push(undefined);}
return parentModel[method].call(model,...args,kwargs);}}}
throw new MockServerError(`Unimplemented ORM method: ${modelName}.${method}`);}
_findAction(id){const strId=String(id);const actions=this.actions.filter((action)=>{for(const identifier of ACTION_IDENTIFIERS){if(String(action[identifier])===strId){return action;}}});if(!actions.length){throw makeServerError({errorName:"odoo.addons.web.controllers.action.MissingActionError",message:`The action ${JSON.stringify(id)} does not exist`,});}
return this._getAction(Object.assign({},...actions));}
_findOrmListeners({method,model}){const callbacks=[this._callOrm];for(const[modelMatchers,methodMatchers,callback]of this._ormListeners){if(match(model,modelMatchers)&&match(method,methodMatchers)){callbacks.unshift(callback);}}
return callbacks;}
_findRouteListeners(url){const fullRoute=url.origin+url.pathname;const listeners=[];for(const[routeRegexes,callback,options]of this._routes){for(const[regex,partialMatch]of routeRegexes){const routePart=partialMatch?url.pathname:fullRoute;const argsMatch=routePart.match(regex);if(argsMatch){listeners.unshift([callback,argsMatch.groups,options]);}}}
return listeners;}
_getAction(rawAction){const mainIdentifier=ACTION_IDENTIFIERS.find((identifier)=>rawAction[identifier]);const id=rawAction[mainIdentifier];const action={binding_type:"action",binding_view_types:"list,form",id,type:ACTION_TYPES.window,xml_id:id,...rawAction,};switch(action.type){case ACTION_TYPES.client:{action.context||={};action.target??="current";break;}
case ACTION_TYPES.embedded:{action.context||={};action.domain||=[];action.filter_ids||=[];action.groups_id||=[];break;}
case ACTION_TYPES.report:{action.binding_type=rawAction.binding_type??"report";action.report_type??="qweb-pdf";action.groups_id||=[];break;}
case ACTION_TYPES.server:{action.available_model_ids||=[];action.child_ids||=[];action.code??="";action.evaluation_type??="value";action.groups_id||=[];action.sequence??=5;action.state??="object_write";action.update_boolean_value??="true";action.update_m2m_operation??="add";action.usage??="ir_actions_server";action.webhook_field_ids||=[];break;}
case ACTION_TYPES.todo:{action.sequence??=10;action.state??="open";break;}
case ACTION_TYPES.url:{action.target??="new";break;}
case ACTION_TYPES.window:{action.context||={};action.embedded_action_ids||=[];action.group_ids||=[];action.limit??=80;action.mobile_view_mode??="kanban";action.target??="current";action.view_ids||=[];action.view_mode??="list,form";for(const embeddedAction of this.actions){if(embeddedAction.type===ACTION_TYPES.embedded&&embeddedAction.parent_action_id===id){action.embedded_action_ids.push(this._getAction(embeddedAction));}}
break;}
default:{if(!(action.type in ACTION_TYPES)){throw new MockServerError(`Invalid action type "${action.type}" in action ${id}`);}}}
return action;}
_getModelDefinition(ModelClass){const model=ModelClass.definition;if(ModelClass._fetch){this._modelNamesToFetch.add(model._name);}
return model;}
async _handleRequest(input,init){const request=new Request(input,init);const url=new URL(request.url);let jsonRpcParams=getJsonRpcParams(init);let error=null;let result=null;const listeners=this._findRouteListeners(url);if(!listeners.length){if(url.origin===mockLocation.origin){error=new MockServerError(`Unimplemented server route: ${url.pathname}`);}else{error=new MockServerError(`Unimplemented server external URL: ${url.origin + url.pathname}`);}}else{for(const[callback,routeParams,{final,pure}]of listeners){try{const callbackResult=await callback.call(this,request,routeParams);if(result instanceof Error){error=callbackResult;}else{result=callbackResult;}}catch(err){error=err instanceof Error?err:new Error(err);}
if(final||error||(result!==null&&result!==undefined)){if(pure||result instanceof Response){jsonRpcParams=null;}
break;}}}
if(jsonRpcParams){if(error){if(error instanceof RPCError){jsonRpcParams.error={...error};}else{jsonRpcParams.error={...makeErrorFromResponse({code:200,data:{name:error.name,message:error.message,subType:error.type,},message:error.message,type:error.name,}),};}
return jsonRpcParams;}else{jsonRpcParams.result=result;return jsonRpcParams;}}else if(error){throw error;}else{return result;}}
_handleWebSocket(webSocket){this._websockets.push(webSocket);}
async _loadModels(){const models=this._modelSpecs;const serverModelInheritances=new Set();this._modelSpecs=[];let serverModels={};if(this._modelNamesToFetch.size){serverModels=await fetchModelDefinitions(this._modelNamesToFetch);this._modelNamesToFetch.clear();}
for(const model of models){if(model._name in serverModels){const{description,fields,inherit,order,parent_name,rec_name,...otherProperties}=serverModels[model._name];if(description){model._description=description;}
if(order){model._order=order;}
if(parent_name){model._parent_name=parent_name;}
if(rec_name){model._rec_name=rec_name;}
if(inherit?.length){const inheritList=new Set(safeSplit(model._inherit));for(const inherittedModelName of inherit){inheritList.add(inherittedModelName);serverModelInheritances.add([model._name,inherittedModelName].join(","));}
model._inherit=[...inheritList].join(",");}
for(const[fieldName,serverField]of Object.entries(fields)){model._fields[fieldName]={...DEFAULT_FIELD_PROPERTIES,...serverField,...model._fields[fieldName],[S_SERVER_FIELD]:true,};}
Object.assign(model,otherProperties);}
if(model._rec_name){if(!(model._rec_name in model._fields)){throw new MockServerError(`Invalid _rec_name "${model._rec_name}" on model "${model._name}": field does not exist`);}}else if("name"in model._fields){model._rec_name="name";}else if("x_name"in model._fields){model._rec_name="x_name";}
if(model._name in this._models){const existingModel=this._models[model._name];for(const fieldName in existingModel._fields){model._fields[fieldName]??=existingModel._fields[fieldName];}
Object.setPrototypeOf(Object.getPrototypeOf(model),existingModel);}else if(model._name in this.env){throw new MockServerError(`Cannot register model "${model._name}": a server environment property with the same name already exists`);}
this._models[model._name]=model;}
for(const model of models){for(const modelName of safeSplit(model._inherit)){if(!modelName){continue;}
const parentModel=this._models[modelName];if(parentModel){for(const fieldName in parentModel._fields){model._fields[fieldName]??=parentModel._fields[fieldName];}}else if(serverModelInheritances.has([model._name,modelName].join(","))){model._inherit=model._inherit.replace(new RegExp(`${modelName},?`),"");}else{throw modelNotFoundError(modelName,"could not inherit from model");}}
for(const[fieldName,field]of Object.entries(model._fields)){if(field.relation&&!this._models[field.relation]){if(field[S_SERVER_FIELD]){delete model._fields[fieldName];continue;}else{throw modelNotFoundError(field.relation,"could not find model");}}
field.name=fieldName;field.string||=getFieldDisplayName(fieldName);const onChange=field.onChange;if(typeof onChange==="function"){model._onChanges[fieldName]||=onChange.bind(model);}
if(field.compute){let computeFn=field.compute;if(typeof computeFn!=="function"){computeFn=model[computeFn];if(typeof computeFn!=="function"){throw new MockServerError(`Could not find compute function "${computeFn}" on model "${model._name}"`);}}
model._computes[fieldName]=computeFn;}else if(field.related){model._related.add(fieldName);}}
const recordsWithoutId=[];const seenIds=new Set();for(const record of model._records){for(const fieldName in record){if(!(fieldName in model._fields)){throw new MockServerError(`Unknown field "${fieldName}" on ${getRecordQualifier(
                                record
                            )} in model "${model._name}"`);}}
if(record.id){if(seenIds.has(record.id)){throw new MockServerError(`Duplicate ID ${record.id} in model "${model._name}"`);}
seenIds.add(record.id);}else{recordsWithoutId.push(record);}
model.push(record);}
model._records=[];for(const record of recordsWithoutId){record.id=model._getNextId();}}
for(const model of models){for(const record of model){model._applyDefaults(record);}
model._applyComputesAndValidate();}
const IrModelFields=this._models["ir.model.fields"];if(IrModelFields){for(const model of models){for(const field of Object.values(model._fields)){if(field.tracking){IrModelFields.create({model:model._name,name:field.name,ttype:field.type,});}}}}}
_onOrmMethod(...args){const[callback]=ensureArray(args.pop());const method=ensureArray(args.pop()||"*");const model=ensureArray(args.pop()||"*");if(typeof callback!=="function"){throw new MockServerError(`onRpc: expected callback to be a function, got: ${callback}`);}
this._ormListeners.push([model,method,callback]);}
_onRoute(routes,callback,options){const routeRegexes=routes.map((route)=>{const regexString=route.replaceAll(R_URL_SPECIAL_CHARACTERS,"\\$&").replaceAll(R_ROUTE_PARAM,(...args)=>{const{name,type}=args.pop();return`(?<${name}>${ALLOWED_CHARS[type] || ALLOWED_CHARS.default}+)`;}).replaceAll(R_WILDCARD,".*");return[new RegExp(`^${regexString}$`,"i"),route.startsWith("/")];});this._routes.push([routeRegexes,callback,options||{}]);}
_onRpc(...args){const ormArgs=[];const routeArgs=[];for(const val of ensureArray(args.shift())){if(typeof val==="string"&&val.includes("/")){routeArgs.push(val);}else{ormArgs.push(val);}}
if(ormArgs.length){this._onOrmMethod(ormArgs,...args);}
if(routeArgs.length){this._onRoute(routeArgs,...args);}
return this;}
async callKw(request){const callNextOrmCallback=()=>{const nextCallback=ormListeners.shift();return nextCallback.call(this,callbackParams);};const{params}=await request.json();params.args||=[];params.kwargs=makeKwArgs(params.kwargs||{});const callbackParams={parent:callNextOrmCallback,request,route:new URL(request.url).pathname,...params,};const ormListeners=this._findOrmListeners(params);while(ormListeners.length){const result=await callNextOrmCallback();if(result!==null&&result!==undefined){return result;}}
return null;}
async loadAction(request){const{params}=await request.json();return this._findAction(params.action_id);}
async loadActionBreadcrumbs(request){const{params}=await request.json();const{actions}=params;return actions.map(({action:actionId,model,resId})=>{let displayName;if(actionId){const action=this._findAction(actionId);if(resId){displayName=this.env[action.res_model].browse(resId)[0].display_name;}else{displayName=action.name;}}else if(model){if(!resId){throw new MockServerError("Actions with a 'model' should also have a 'resId'");}
displayName=this.env[model].browse(resId)[0].display_name;}else{throw new MockServerError("Actions should have either an 'action' (ID or path) or a 'model'");}
return{display_name:displayName};});}
async loadBundle(request){const initiatorTestId=getCurrent().test?.id;if(initiatorTestId){const result=await globalCachedFetch(request.url);if(initiatorTestId===getCurrent().test?.id){return result;}}
return new Promise(()=>{});}
async loadImage(request,{id,model,field}){return`<fake url to record ${id} on ${model}.${field}>`;}
async loadMenus(){const allChildIds=new Set();const menuDict={};const menuStack=[{...ROOT_MENU,children:this.menus}];while(menuStack.length){const menu=menuStack.shift();const childIds=new Set();menuDict[menu.id]={...menuDict[menu.id],...menu};for(const childMenuOrId of menuDict[menu.id].children){let childId=childMenuOrId;if(isObject(childMenuOrId)){childId=childMenuOrId.id;menuStack.push({appID:childId,children:[],name:`App${childId}`,...childMenuOrId,});}
allChildIds.add(childId);childIds.add(childId);}
menuDict[menu.id].children=[...childIds].sort((a,b)=>a-b);}
const missingMenuIds=[...allChildIds].filter((id)=>!(id in menuDict));if(missingMenuIds.length){throw new MockServerError(`Missing menu ID(s): ${missingMenuIds.join(", ")}`);}
return menuDict;}
async loadTranslations(){const langParameters={...this._lang_parameters};if(typeof langParameters.grouping!=="string"){langParameters.grouping=JSON.stringify(langParameters.grouping);}
return{lang:serverState.lang,lang_parameters:langParameters,modules:this._modules,multi_lang:serverState.multiLang,};}
async resequence(request){const{params}=await request.json();const offset=params.offset?Number(params.offset):0;const field=params.field||"sequence";if(!(field in this.env[params.model]._fields)){return false;}
for(const index in params.ids){const record=this.env[params.model].find((r)=>r.id===params.ids[index]);record[field]=Number(index)+offset;}
return true;}}
__exports.authenticate=authenticate;function authenticate(login,password){const{env}=MockServer;const[user]=env["res.users"]._filter([["login","=",login],["password","=",password],],{active_test:false});authenticateUser(user);env.cookie.set("authenticated_user_sid",env.cookie.get("sid"));}
__exports.defineActions=defineActions;function defineActions(actions,options){before(()=>_defineParams({actions},{mode:"add",...options}));}
__exports.defineMenus=defineMenus;function defineMenus(menus,options){before(()=>_defineParams({menus},{mode:"add",...options}));}
__exports.defineModels=defineModels;function defineModels(ModelClasses,options){const models=Object.values(ModelClasses);for(const ModelClass of models){if(seenModels.has(ModelClass)){continue;}
seenModels.add(ModelClass);if(ModelClass._fetch){registerModelToFetch(ModelClass.getModelName());}}
before(()=>_defineParams({models},{mode:"add",...options}));}
__exports.defineParams=defineParams;function defineParams(params,options){before(()=>_defineParams(params,options));}
__exports.logout=logout;function logout(){const{env}=MockServer;if(env.cookie.get("authenticated_user_sid")===env.cookie.get("sid")){env.cookie.delete("authenticated_user_sid");}
env.cookie.delete("sid");const[publicUser]=env["res.users"].browse(serverState.publicUserId,{active_test:false,});authenticate(publicUser.login,publicUser.password);}
__exports.makeMockServer=makeMockServer;async function makeMockServer(){return getCurrentMockServer().start();}
__exports.onRpc=onRpc;function onRpc(...args){before(()=>_defineParams({routes:[args]},{mode:"add"}));}
__exports.stepAllNetworkCalls=stepAllNetworkCalls;function stepAllNetworkCalls(){onRpc("/*",(request)=>{const route=new URL(request.url).pathname;let match=route.match(R_DATASET_ROUTE);if(match){return void expect.step(match.groups?.step||route);}
match=route.match(R_WEBCLIENT_ROUTE);if(match){return void expect.step(match.groups?.step||route);}
return void expect.step(route);});}
__exports.withUser=withUser;async function withUser(userId,fn){const{env}=MockServer;const currentUser=env.user;const[targetUser]=env["res.users"].browse(userId,{active_test:false});authenticateUser(targetUser);let result;try{result=await fn();}finally{if(currentUser){authenticateUser(currentUser);}else{logout();}}
return result;}
const S_MODEL_LOADED=__exports.S_MODEL_LOADED=Symbol("model-loaded");return __exports;});;

/* /web/static/tests/_framework/mock_server/mock_server_utils.js */
odoo.define('@web/../tests/_framework/mock_server/mock_server_utils',['@web/core/network/rpc'],function(require){'use strict';let __exports={};const{makeErrorFromResponse}=require("@web/core/network/rpc");const KWARGS_SYMBOL=Symbol("is_kwargs");__exports.makeKwArgs=makeKwArgs;function makeKwArgs(kwargs){kwargs[KWARGS_SYMBOL]=true;return kwargs;}
__exports.getKwArgs=getKwArgs;function getKwArgs(allArgs,...argNames){const args=[...allArgs];const kwargs=args.at(-1)?.[KWARGS_SYMBOL]?args.pop():makeKwArgs({});if(args.length>argNames.length){throw new MockServerError("More positional arguments than there are given argument names");}
for(let i=0;i<args.length;i++){if(args[i]!==null&&args[i]!==undefined){kwargs[argNames[i]]=args[i];}}
return kwargs;}
__exports.getRecordQualifier=getRecordQualifier;function getRecordQualifier(record){if(record.id){return`record #${record.id}`;}
const name=record.display_name||record.name;if(name){return`record named "${name}"`;}
return"anonymous record";}
__exports.makeServerError=makeServerError;function makeServerError({code,context,description,message,subType,errorName,type,args,}={}){return makeErrorFromResponse({code:code||200,data:{name:errorName||`odoo.exceptions.${type || "UserError"}`,debug:"traceback",arguments:args||[],context:context||{},subType,message:description||message,},message:message||"Odoo Server Error",type:"server",});}
__exports.safeSplit=safeSplit;function safeSplit(value,separator){return value?String(value).trim().split(separator||","):[];}
__exports.unmakeKwArgs=unmakeKwArgs;function unmakeKwArgs(kwargs){delete kwargs[KWARGS_SYMBOL];return kwargs;}
const MockServerError=__exports.MockServerError=class MockServerError extends Error{name="MockServerError";}
return __exports;});;

/* /web/static/tests/_framework/mock_server_state.hoot.js */
odoo.define('@web/../tests/_framework/mock_server_state.hoot',['@odoo/hoot','@odoo/owl'],function(require){'use strict';let __exports={};const{after,before,beforeEach,createJobScopedGetter}=require("@odoo/hoot");const{validateType}=require("@odoo/owl");const{view_info}=odoo.__session_info__||{};delete odoo.__session_info__;const{Settings}=luxon;const applyDefaults=()=>{Object.assign(Settings,DEFAULT_LUXON_SETTINGS);notifySubscribers();};const notifySubscribers=()=>{for(const[target,callback]of subscriptions){const descriptors=Object.getOwnPropertyDescriptors(callback(serverState));Object.defineProperties(target,descriptors);}};const DEFAULT_LUXON_SETTINGS={defaultLocale:Settings.defaultLocale,defaultNumberingSystem:Settings.defaultNumberingSystem,defaultOutputCalendar:Settings.defaultOutputCalendar,defaultZone:Settings.defaultZone,defaultWeekSettings:Settings.defaultWeekSettings,};const SERVER_STATE_VALUES={companies:[{id:1,name:"Hermit",},],currencies:[{id:1,name:"USD",position:"before",symbol:"$",},{id:2,name:"EUR",position:"after",symbol:"€",},],db:"test",debug:"",groupId:11,lang:"en",multiLang:false,odoobotId:418,partnerId:17,partnerName:"Mitchell Admin",publicPartnerId:18,publicPartnerName:"Public user",publicUserId:8,serverVersion:[1,0,0,"final",0,""],timezone:"taht",userContext:{},userId:7,view_info,};const SERVER_STATE_VALUES_SCHEMA={companies:{type:Array,element:Object},currencies:{type:Array,element:Object},db:String,debug:String,groupId:[Number,{value:false}],lang:String,multiLang:Boolean,odoobotId:[Number,{value:false}],partnerId:[Number,{value:false}],partnerName:String,publicPartnerId:[Number,{value:false}],publicPartnerName:String,publicUserId:Number,serverVersion:{type:Array,element:[String,Number]},timezone:String,userContext:Object,userId:[Number,{value:false}],view_info:Object,};const getServerStateValues=createJobScopedGetter((previousValues)=>({...JSON.parse(JSON.stringify(SERVER_STATE_VALUES)),...previousValues,}),applyDefaults);const subscriptions=new Map([[odoo,({db,debug,serverVersion})=>({...odoo,debug,info:{db,server_version:serverVersion.slice(0,2).join("."),server_version_info:serverVersion,isEnterprise:serverVersion.slice(-1)[0]==="e",},isReady:true,}),],]);__exports.onServerStateChange=onServerStateChange;function onServerStateChange(target,callback){before(()=>{subscriptions.set(target,callback);});after(()=>{subscriptions.delete(target);});}
const serverState=__exports.serverState=new Proxy(SERVER_STATE_VALUES,{deleteProperty(_target,p){return Reflect.deleteProperty(getServerStateValues(),p);},get(_target,p){return Reflect.get(getServerStateValues(),p);},has(_target,p){return Reflect.has(getServerStateValues(),p);},set(_target,p,newValue){if(p in SERVER_STATE_VALUES_SCHEMA&&newValue!==null&&newValue!==undefined){const errorMessage=validateType(p,newValue,SERVER_STATE_VALUES_SCHEMA[p]);if(errorMessage){throw new TypeError(errorMessage);}}
const result=Reflect.set(getServerStateValues(),p,newValue);if(result){notifySubscribers();}
return result;},});beforeEach(applyDefaults,{global:true});return __exports;});;

/* /web/static/tests/_framework/mock_session.hoot.js */
odoo.define('@web/../tests/_framework/mock_session.hoot',['@web/../tests/_framework/mock_server_state.hoot'],function(require){'use strict';let __exports={};const{onServerStateChange,serverState}=require("@web/../tests/_framework/mock_server_state.hoot");const makeSession=({companies,db,lang,partnerId,partnerName,serverVersion,timezone,userContext,userId,view_info,})=>({active_ids_limit:20000,bundle_params:{debug:new URLSearchParams(location.search).get("debug"),lang,},cache_hashes:{load_menus:"164b675eb9bf49f8bca52e350cd81482a8cf0d0c1c8a47d99bd063c0a0bf4f0d",translations:"f17c8e4bb0fd4d5db2615d28713486df97853a8f",},can_insert_in_spreadsheet:true,db,display_switch_company_menu:false,home_action_id:false,is_admin:true,is_internal_user:true,is_system:true,max_file_upload_size:134217728,name:partnerName,partner_display_name:partnerName,partner_id:partnerId,profile_collectors:null,profile_params:null,profile_session:null,server_version:serverVersion.slice(0,2).join("."),server_version_info:serverVersion,show_effect:true,uid:userId,user_companies:{allowed_companies:Object.fromEntries(companies.map((company)=>[company.id,company])),current_company:companies[0]?.id,disallowed_ancestor_companies:{},},user_context:{...userContext,lang,tz:timezone,uid:userId,},user_id:[userId],username:"admin",["web.base.url"]:"http://localhost:8069",view_info,});__exports.mockSessionFactory=mockSessionFactory;function mockSessionFactory(){return()=>{const session=makeSession(serverState);onServerStateChange(session,makeSession);return{session};};}
return __exports;});;

/* /web/static/tests/_framework/mock_templates.hoot.js */
odoo.define('@web/../tests/_framework/mock_templates.hoot',[],function(require){'use strict';let __exports={};const replaceAttributes=(template)=>{for(const[tagName,value]of SRC_REPLACERS){for(const prefix of ATTRIBUTE_PREFIXES){const targetAttribute=`${prefix}src`;const dataAttribute=`${prefix}data-src`;for(const element of template.querySelectorAll(`${tagName}[${targetAttribute}]`)){element.setAttribute(dataAttribute,element.getAttribute(targetAttribute));if(prefix){element.removeAttribute(targetAttribute);}
element.setAttribute("src",value);}}}};const ONE_FUSCHIA_PIXEL_IMG="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z9DwHwAGBQKA3H7sNwAAAABJRU5ErkJggg==";const SRC_REPLACERS=[["iframe",""],["img",ONE_FUSCHIA_PIXEL_IMG],];const ATTRIBUTE_PREFIXES=["","t-att-","t-attf-"];const{loader}=odoo;__exports.makeTemplateFactory=makeTemplateFactory;function makeTemplateFactory(name,factory){return()=>{if(loader.modules.has(name)){return loader.modules.get(name);}
const compiledTemplates=new Map();const factoryFn=factory.fn;factory.fn=(...args)=>{const exports=factoryFn(...args);const{clearProcessedTemplates,getTemplate}=exports;exports.getTemplate=function mockedGetTemplate(name){if(!this){return getTemplate(name);}
const rawTemplate=getTemplate(name)||this.rawTemplates[name];if(typeof rawTemplate==="function"&&!(rawTemplate instanceof Element)){return rawTemplate;}
if(!compiledTemplates.has(rawTemplate)){compiledTemplates.set(rawTemplate,this._compileTemplate(name,rawTemplate));}
return compiledTemplates.get(rawTemplate);};exports.clearProcessedTemplates=function mockedClearProcessedTemplates(){compiledTemplates.clear();return clearProcessedTemplates(...arguments);};exports.registerTemplateProcessor(replaceAttributes);return exports;};return loader.startModule(name);};}
return __exports;});;

/* /web/static/tests/_framework/mock_user.hoot.js */
odoo.define('@web/../tests/_framework/mock_user.hoot',['@web/../tests/_framework/mock_server_state.hoot'],function(require){'use strict';let __exports={};const{onServerStateChange}=require("@web/../tests/_framework/mock_server_state.hoot");__exports.mockUserFactory=mockUserFactory;function mockUserFactory(name,{fn}){return(requireModule,...args)=>{const{session}=requireModule("@web/session");const userModule=fn(requireModule,...args);onServerStateChange(userModule.user,()=>userModule._makeUser(session));return userModule;};}
return __exports;});;

/* /web/static/tests/_framework/module_set.hoot.js */
odoo.define('@web/../tests/_framework/module_set.hoot',['@odoo/hoot','@web/../tests/_framework/mock_browser.hoot','@web/../tests/_framework/mock_currency.hoot','@web/../tests/_framework/mock_session.hoot','@web/../tests/_framework/mock_templates.hoot','@web/../tests/_framework/mock_user.hoot'],function(require){'use strict';let __exports={};const{Deferred,delay,describe,dryRun,globals,start,stop,watchAddedNodes,watchKeys,watchListeners,}=require("@odoo/hoot");const{mockBrowserFactory}=require("@web/../tests/_framework/mock_browser.hoot");const{mockCurrencyFactory}=require("@web/../tests/_framework/mock_currency.hoot");const{mockSessionFactory}=require("@web/../tests/_framework/mock_session.hoot");const{makeTemplateFactory}=require("@web/../tests/_framework/mock_templates.hoot");const{mockUserFactory}=require("@web/../tests/_framework/mock_user.hoot");const{fetch:realFetch}=globals;const{define,loader}=odoo;function clearObject(object){for(const key in object){delete object[key];}}
async function defineModuleSet(fileSuffix,entryPoints,additionalAddons){const moduleSet={};if(additionalAddons.has("*")){moduleSet.addonsKey="*";moduleSet.moduleNames=sortedModuleNames.filter((name)=>!name.endsWith(fileSuffix));}else{for(const entryPoint of entryPoints){additionalAddons.add(getAddonName(entryPoint));}
const addons=await fetchDependencies(additionalAddons);for(const addon in AUTO_INCLUDED_ADDONS){if(addons.has(addon)){for(const toInclude of AUTO_INCLUDED_ADDONS[addon]){addons.add(toInclude);}}}
const filter=(path)=>addons.has(getAddonName(path));const joinedAddons=[...addons].sort().join(",");if(!moduleNamesCache.has(joinedAddons)){moduleNamesCache.set(joinedAddons,sortedModuleNames.filter((name)=>!name.endsWith(fileSuffix)&&filter(name)));}
moduleSet.addonsKey=joinedAddons;moduleSet.filter=filter;moduleSet.moduleNames=moduleNamesCache.get(joinedAddons);}
return moduleSet;}
async function describeDrySuite(fileSuffix,entryPoints){const moduleSet=await defineModuleSet(fileSuffix,entryPoints,new Set(["*"]));const moduleSetLoader=new ModuleSetLoader(moduleSet);moduleSetLoader.setup();for(const entryPoint of entryPoints){describe(getSuitePath(entryPoint),()=>{const fullModuleName=entryPoint+fileSuffix;const module=moduleSetLoader.startModule(fullModuleName);const exports=Object.keys(module||{});if(exports.length){throw new Error(`Test files cannot have exports, found the following exported member(s) in module ${fullModuleName}:${exports
                        .map((name) => `\n-${name}`)
                        .join("")}`);}});}
await moduleSetLoader.cleanup();}
async function fetchDependencies(addons){const addonsToFetch=[];for(const addon of addons){if(!dependencyCache[addon]&&!DEFAULT_ADDONS.includes(addon)){addonsToFetch.push(addon);dependencyCache[addon]=new Deferred();}}
if(addonsToFetch.length){if(!dependencyBatch.length){dependencyBatchPromise=Deferred.resolve().then(()=>{const module_names=[...new Set(dependencyBatch)];dependencyBatch=[];return orm("ir.module.module.dependency","all_dependencies",[],{module_names});});}
dependencyBatch.push(...addonsToFetch);dependencyBatchPromise.then((allDependencies)=>{for(const[moduleName,dependencyNames]of Object.entries(allDependencies)){dependencyCache[moduleName]||=new Deferred();dependencyCache[moduleName].resolve();dependencies[moduleName]=dependencyNames.filter((dep)=>!DEFAULT_ADDONS.includes(dep));}
resolveAddonDependencies(dependencies);});}
await Promise.all([...addons].map((addon)=>dependencyCache[addon]));return getDependencies(addons);}
function findMockFactory(name){if(MODULE_MOCKS_BY_NAME.has(name)){return MODULE_MOCKS_BY_NAME.get(name);}
for(const[key,factory]of MODULE_MOCKS_BY_REGEX){if(key instanceof RegExp&&key.test(name)){return factory;}}
return null;}
function freezeField(field){delete field.name;if(field.groupable){delete field.groupable;}
if(!field.readonly&&!field.related){delete field.readonly;}
if(!field.required){delete field.required;}
if(field.searchable){delete field.searchable;}
if(field.sortable){delete field.sortable;}
if(field.store&&!field.related){delete field.store;}
return Object.freeze(field);}
function freezeModel(model){if(model.fields){for(const[fieldName,field]of Object.entries(model.fields)){model.fields[fieldName]=freezeField(field);}
Object.freeze(model.fields);}
if(model.inherit){if(model.inherit.length){model.inherit=model.inherit.filter((m)=>m!=="base");}
if(!model.inherit.length){delete model.inherit;}}
if(model.order==="id"){delete model.order;}
if(model.parent_name==="parent_id"){delete model.parent_name;}
if(model.rec_name==="name"){delete model.rec_name;}
return Object.freeze(model);}
function getAddonName(name){return name.match(R_PATH_ADDON)?.[1];}
function getDependencies(addons){const result=new Set(DEFAULT_ADDONS);for(const addon of addons){if(DEFAULT_ADDONS.includes(addon)){continue;}
result.add(addon);for(const dep of dependencies[addon]){result.add(dep);}}
return result;}
function getSuitePath(name){return name.replace("../tests/","");}
function makeFixedFactory(name){return()=>{if(!loader.modules.has(name)){loader.startModule(name);}
return loader.modules.get(name);};}
async function orm(model,method,args,kwargs){const response=await realFetch(`/web/dataset/call_kw/${model}/${method}`,{body:JSON.stringify({id:nextRpcId++,jsonrpc:"2.0",method:"call",params:{args,kwargs,method,model},}),headers:{"Content-Type":"application/json",},method:"POST",});const{error,result}=await response.json();if(error){throw error;}
return result;}
function resolveAddonDependencies(dependencies){const findJob=()=>Object.entries(remaining).find(([,deps])=>deps.every((dep)=>dep in solved));const remaining={...dependencies};const solved={};let entry;while((entry=findJob())){const[name,deps]=entry;solved[name]=[...new Set(deps.flatMap((dep)=>[dep,...solved[dep]]))];delete remaining[name];}
const remainingKeys=Object.keys(remaining);if(remainingKeys.length){throw new Error(`Unresolved dependencies: ${remainingKeys.join(", ")}`);}
Object.assign(dependencies,solved);}
function unfreezeModel(model){const fields=Object.create(null);if(model.fields){for(const[fieldName,field]of Object.entries(model.fields)){fields[fieldName]={...field};}}
return{...model,fields};}
async function __gcAndLogMemory(label,testCount){if(typeof window.gc!=="function"){return;}
const textarea=document.createElement("textarea");document.body.appendChild(textarea);textarea.value="aaa";textarea.focus();textarea.remove();await window.gc({type:"major",execution:"async"});const logs=[`[MEMINFO] ${label} (after GC)`,"- used:",window.performance.memory.usedJSHeapSize,"- total:",window.performance.memory.totalJSHeapSize,"- limit:",window.performance.memory.jsHeapSizeLimit,];if(Number.isInteger(testCount)){logs.push("- tests:",testCount);}
console.log(...logs);}
class ModuleSetLoader extends loader.constructor{cleanups=[];preventGlobalDefine=false;constructor(moduleSet){super();this.factories=new Map(loader.factories);this.modules=new Map(loader.modules);this.moduleSet=moduleSet;odoo.define=this.define.bind(this);odoo.loader=this;}
addJob(name){if(this.canAddModule(name)){super.addJob(...arguments);}}
canAddModule(name){const{filter}=this.moduleSet;return!filter||filter(name)||R_DEFAULT_MODULE.test(name);}
async cleanup(){await delay();odoo.define=define;odoo.loader=loader;while(this.cleanups.length){this.cleanups.pop()();}}
define(name,deps,factory){if(!this.preventGlobalDefine&&!loader.factories.has(name)){loader.define(name,deps,factory,true);sortedModuleNames.push(name);moduleNamesCache.clear();}
return super.define(...arguments);}
setup(){this.cleanups.push(watchKeys(window.odoo),watchKeys(window,ALLOWED_GLOBAL_KEYS),watchListeners(window),watchAddedNodes(window));for(const name of this.moduleSet.moduleNames){const mockFactory=findMockFactory(name);if(mockFactory){this.factories.set(name,{deps:[],fn:mockFactory(name,this.factories.get(name)),});}
if(!this.modules.has(name)){this.startModule(name);}}}
startModule(name){if(this.canAddModule(name)){return super.startModule(...arguments);}
this.jobs.delete(name);return null;}}
const ALLOWED_GLOBAL_KEYS=["ace","Chart","DiffMatchPatch","DOMPurify","FullCalendar","L","lamejs","luxon","odoo","owl","pdfjsLib","Popper","Prism","SignaturePad","StackTrace","ZXing",];const AUTO_INCLUDED_ADDONS={spreadsheet:["odoo"],web_enterprise:["web_gantt","web_grid","web_map"],};const CSRF_TOKEN=odoo.csrf_token;const DEFAULT_ADDONS=["base","web"];const MODULE_MOCKS_BY_NAME=new Map([["@web/core/template_inheritance",makeFixedFactory],["@web/core/browser/browser",mockBrowserFactory],["@web/core/currency",mockCurrencyFactory],["@web/core/templates",makeTemplateFactory],["@web/core/user",mockUserFactory],["@web/session",mockSessionFactory],]);const MODULE_MOCKS_BY_REGEX=new Map([[/\.bundle\.xml$/,makeFixedFactory],]);const R_DEFAULT_MODULE=/^@odoo\/(owl|hoot)/;const R_PATH_ADDON=/^[@/]?(\w+)/;const TEMPLATE_MODULE_NAME="@web/core/templates";const dependencies={};const dependencyCache={};const globalFetchCache=Object.create(null);const modelsToFetch=new Set();const moduleNamesCache=new Map();const serverModelCache=new Map();const sortedModuleNames=[];let dependencyBatch=[];let dependencyBatchPromise=null;let nextRpcId=1e9;__exports.clearServerModelCache=clearServerModelCache;function clearServerModelCache(){serverModelCache.clear();}
__exports.fetchModelDefinitions=fetchModelDefinitions;async function fetchModelDefinitions(modelNames){const namesList=[...modelsToFetch];if(namesList.length){const formData=new FormData();formData.set("csrf_token",CSRF_TOKEN);formData.set("model_names",JSON.stringify(namesList));const response=await realFetch("/web/model/get_definitions",{body:formData,method:"POST",});if(!response.ok){const[s,some,does]=namesList.length===1?["","this","does"]:["s","some or all of these","do"];const message=`Could not fetch definition${s} for server model${s} "${namesList.join(
                `", "`
            )}": ${some} model${s} ${does} not exist`;throw new Error(message);}
const modelDefs=await response.json();for(const[modelName,modelDef]of Object.entries(modelDefs)){serverModelCache.set(modelName,freezeModel(modelDef));modelsToFetch.delete(modelName);}}
const result=Object.create(null);for(const modelName of modelNames){result[modelName]=unfreezeModel(serverModelCache.get(modelName));}
return result;}
__exports.globalCachedFetch=globalCachedFetch;function globalCachedFetch(input,init){if(init?.method&&init.method.toLowerCase()!=="get"){throw new Error(`cannot use a global cached fetch with HTTP method "${init.method}"`);}
const key=String(input);if(!(key in globalFetchCache)){globalFetchCache[key]=realFetch(input,init).catch((reason)=>{delete globalFetchCache[key];throw reason;});}
return globalFetchCache[key].then((response)=>response.clone());}
__exports.registerModelToFetch=registerModelToFetch;function registerModelToFetch(modelName){if(!serverModelCache.has(modelName)){modelsToFetch.add(modelName);}}
__exports.runTests=runTests;async function runTests(options){const{fileSuffix=""}=options||{};const errors=loader.findErrors(loader.factories.keys());delete errors.unloaded;if(Object.keys(errors).length){return loader.reportErrors(errors);}
const defs={};const testModuleNames=[];for(const[name,{deps}]of loader.factories){if(name.endsWith(fileSuffix)){const baseName=name.slice(0,-fileSuffix.length);testModuleNames.push(baseName);}
const[modDef,...depDefs]=[name,...deps].map((dep)=>(defs[dep]||=new Deferred()));Promise.all(depDefs).then(()=>{sortedModuleNames.push(name);modDef.resolve();});}
await Promise.all(Object.values(defs));const{suites}=await dryRun(()=>describeDrySuite(fileSuffix,testModuleNames));const filteredSuitePaths=new Set(suites.map((s)=>s.fullName));let currentAddonsKey="";for(const moduleName of testModuleNames){const suitePath=getSuitePath(moduleName);if(!filteredSuitePaths.has(suitePath)){continue;}
const moduleSet=await defineModuleSet(fileSuffix,[moduleName],new Set());const moduleSetLoader=new ModuleSetLoader(moduleSet);if(currentAddonsKey!==moduleSet.addonsKey){if(moduleSetLoader.modules.has(TEMPLATE_MODULE_NAME)){const templateModule=moduleSetLoader.modules.get(TEMPLATE_MODULE_NAME);templateModule.setUrlFilters(moduleSet.filter?[moduleSet.filter]:[]);templateModule.clearProcessedTemplates();}
currentAddonsKey=moduleSet.addonsKey;}
const suite=describe(suitePath,()=>{moduleSetLoader.setup();moduleSetLoader.startModule(moduleName+fileSuffix);});const running=await start(suite);await moduleSetLoader.cleanup();await __gcAndLogMemory(suite.fullName,suite.reporting.tests);if(!running){break;}}
await stop();moduleNamesCache.clear();serverModelCache.clear();clearObject(dependencies);clearObject(dependencyCache);clearObject(globalFetchCache);const templateModule=loader.modules.get(TEMPLATE_MODULE_NAME);if(templateModule){templateModule.setUrlFilters([]);templateModule.clearProcessedTemplates();}
await __gcAndLogMemory("tests done");}
return __exports;});;

/* /web/static/tests/_framework/patch_test_helpers.js */
odoo.define('@web/../tests/_framework/patch_test_helpers',['@odoo/hoot','@web/core/utils/patch'],function(require){'use strict';let __exports={};const{after,onTimeZoneChange}=require("@odoo/hoot");const{patch}=require("@web/core/utils/patch");const{FixedOffsetZone,IANAZone,Settings}=luxon;onTimeZoneChange((tz)=>{let defaultZone;if(typeof tz==="string"){defaultZone=IANAZone.create(tz);}else{const offset=new Date().getTimezoneOffset();defaultZone=FixedOffsetZone.instance(-offset);}
patchWithCleanup(Settings,{defaultZone});});__exports.patchWithCleanup=patchWithCleanup;function patchWithCleanup(obj,patchValue){const unpatch=patch(obj,patchValue);after(unpatch);return unpatch;}
return __exports;});;

/* /web/static/tests/_framework/resize_observer_error_catcher.js */
odoo.define('@web/../tests/_framework/resize_observer_error_catcher',['@odoo/hoot'],function(require){'use strict';let __exports={};const{afterEach,onError}=require("@odoo/hoot");__exports.preventResizeObserverError=preventResizeObserverError;function preventResizeObserverError(){let resizeObserverErrorCount=0;onError((ev)=>{if(ev.message==="ResizeObserver loop completed with undelivered notifications."){if(resizeObserverErrorCount<1){ev.preventDefault();}
resizeObserverErrorCount++;}});afterEach(()=>{resizeObserverErrorCount=0;});}
return __exports;});;

/* /web/static/tests/_framework/search_test_helpers.js */
odoo.define('@web/../tests/_framework/search_test_helpers',['@odoo/hoot','@odoo/owl','@web/../tests/_framework/component_test_helpers','@web/../tests/_framework/dom_test_helpers','@web/../tests/_framework/env_test_helpers','@web/search/with_search/with_search','@web/views/view'],function(require){'use strict';let __exports={};const{queryAll,queryAllTexts,queryOne,queryText}=require("@odoo/hoot");const{Component,xml}=require("@odoo/owl");const{findComponent,mountWithCleanup}=require("@web/../tests/_framework/component_test_helpers");const{contains}=require("@web/../tests/_framework/dom_test_helpers");const{getMockEnv,makeMockEnv}=require("@web/../tests/_framework/env_test_helpers");const{WithSearch}=require("@web/search/with_search/with_search");const{getDefaultConfig}=require("@web/views/view");const ensureSearchView=async()=>{if(getMockEnv().isSmall&&queryAll`.o_control_panel_navigation`.length&&!queryAll`.o_searchview`.length){await contains(`.o_control_panel_navigation button`).click();}};const ensureSearchBarMenu=async()=>{if(!queryAll`.o_search_bar_menu`.length){await toggleSearchBarMenu();}};function filterPropsForComponent(Component,props){if(Component.props){let componentKeys=null;if(Component.props instanceof Array){componentKeys=Component.props.map((x)=>x.replace("?",""));}else{componentKeys=Object.keys(Component.props);}
if(componentKeys.includes("*")){return props;}else{return Object.keys(props).filter((k)=>componentKeys.includes(k)).reduce((o,k)=>{o[k]=props[k];return o;},{});}}else{return props;}}
__exports.mountWithSearch=mountWithSearch;async function mountWithSearch(componentConstructor,searchProps={},config={}){class ComponentWithSearch extends Component{static template=xml`
            <WithSearch t-props="withSearchProps" t-slot-scope="search">
                <t t-component="component" t-props="getProps(search)"/>
            </WithSearch>
        `;static components={WithSearch};static props=["*"];setup(){this.withSearchProps=searchProps;this.component=componentConstructor;}
getProps(search){const props={context:search.context,domain:search.domain,groupBy:search.groupBy,orderBy:search.orderBy,comparison:search.comparison,display:search.display,};return filterPropsForComponent(componentConstructor,props);}}
const fullConfig={...getDefaultConfig(),...config};const env=await makeMockEnv({config:fullConfig});const root=await mountWithCleanup(ComponentWithSearch,{env});return findComponent(root,(component)=>component instanceof componentConstructor);}
__exports.toggleMenu=toggleMenu;async function toggleMenu(label){await contains(`button.o-dropdown:text(${label})`).click();}
__exports.toggleMenuItem=toggleMenuItem;async function toggleMenuItem(label){const target=queryOne`.o_menu_item:text(${label})`;if(target.classList.contains("dropdown-toggle")){await contains(target).hover();}else{await contains(target).click();}}
__exports.toggleMenuItemOption=toggleMenuItemOption;async function toggleMenuItemOption(itemLabel,optionLabel){const{parentElement:root}=queryOne`.o_menu_item:text(${itemLabel})`;const target=queryOne(`.o_item_option:text(${optionLabel})`,{root});if(target.classList.contains("dropdown-toggle")){await contains(target).hover();}else{await contains(target).click();}}
__exports.isItemSelected=isItemSelected;function isItemSelected(label){return queryOne`.o_menu_item:text(${label})`.classList.contains("selected");}
__exports.isOptionSelected=isOptionSelected;function isOptionSelected(itemLabel,optionLabel){const{parentElement:root}=queryOne`.o_menu_item:text(${itemLabel})`;return queryOne(`.o_item_option:text(${optionLabel})`,{root}).classList.contains("selected");}
__exports.getMenuItemTexts=getMenuItemTexts;function getMenuItemTexts(){return queryAllTexts`.dropdown-menu .o_menu_item`;}
__exports.getButtons=getButtons;function getButtons(){return queryAll`.o_control_panel_breadcrumbs button`;}
__exports.getVisibleButtons=getVisibleButtons;function getVisibleButtons(){return queryAll`.o_control_panel_breadcrumbs button:visible, .o_control_panel_actions button:visible`;}
__exports.toggleFilterMenu=toggleFilterMenu;async function toggleFilterMenu(){await ensureSearchBarMenu();await contains(`.o_filter_menu button.dropdown-toggle`).click();}
__exports.openAddCustomFilterDialog=openAddCustomFilterDialog;async function openAddCustomFilterDialog(){await ensureSearchBarMenu();await contains(`.o_filter_menu .o_menu_item.o_add_custom_filter`).click();}
__exports.toggleGroupByMenu=toggleGroupByMenu;async function toggleGroupByMenu(){await ensureSearchBarMenu();await contains(`.o_group_by_menu .dropdown-toggle`).click();}
__exports.selectGroup=selectGroup;async function selectGroup(fieldName){await ensureSearchBarMenu();await contains(`.o_add_custom_group_menu`).select(fieldName);}
__exports.toggleFavoriteMenu=toggleFavoriteMenu;async function toggleFavoriteMenu(){await ensureSearchBarMenu();await contains(`.o_favorite_menu .dropdown-toggle`).click();}
__exports.deleteFavorite=deleteFavorite;async function deleteFavorite(text){await ensureSearchBarMenu();await contains(`.o_favorite_menu .o_menu_item:text(${text}) i.fa-trash-o`).click();}
__exports.toggleSaveFavorite=toggleSaveFavorite;async function toggleSaveFavorite(){await ensureSearchBarMenu();await contains(`.o_favorite_menu .o_add_favorite`).click();}
__exports.editFavoriteName=editFavoriteName;async function editFavoriteName(name){await ensureSearchBarMenu();await contains(`.o_favorite_menu .o_add_favorite + .o_accordion_values input[type="text"]`).edit(name,{confirm:false});}
__exports.saveFavorite=saveFavorite;async function saveFavorite(){await ensureSearchBarMenu();await contains(`.o_favorite_menu .o_add_favorite + .o_accordion_values button`).click();}
__exports.toggleComparisonMenu=toggleComparisonMenu;async function toggleComparisonMenu(){await ensureSearchBarMenu();await contains(`.o_comparison_menu button.dropdown-toggle`).click();}
__exports.getFacetTexts=getFacetTexts;function getFacetTexts(){return queryAllTexts(`.o_searchview_facet`);}
__exports.removeFacet=removeFacet;async function removeFacet(label){await ensureSearchView();await contains(`.o_searchview_facet:text(${label}) .o_facet_remove`).click();}
__exports.editSearch=editSearch;async function editSearch(value){await ensureSearchView();await contains(`.o_searchview input`).edit(value,{confirm:false});}
__exports.validateSearch=validateSearch;async function validateSearch(){await ensureSearchView();await contains(`.o_searchview input`).press("Enter");}
__exports.switchView=switchView;async function switchView(viewType){await contains(`button.o_switch_view.o_${viewType}`).click();}
__exports.getPagerValue=getPagerValue;function getPagerValue(root){return queryText(".o_pager .o_pager_value",{root}).split(/\s*-\s*/).map(Number);}
__exports.getPagerLimit=getPagerLimit;function getPagerLimit(root){return parseInt(queryText(".o_pager .o_pager_limit",{root}),10);}
__exports.pagerNext=pagerNext;async function pagerNext(root){await contains(".o_pager button.o_pager_next",{root}).click();}
__exports.pagerPrevious=pagerPrevious;async function pagerPrevious(root){await contains(".o_pager button.o_pager_previous",{root}).click();}
__exports.editPager=editPager;async function editPager(value){await contains(`.o_pager .o_pager_limit`).edit(value);}
__exports.toggleActionMenu=toggleActionMenu;async function toggleActionMenu(){await contains(".o_cp_action_menus .dropdown-toggle").click();}
__exports.toggleSearchBarMenu=toggleSearchBarMenu;async function toggleSearchBarMenu(){await ensureSearchView();await contains(`.o_searchview_dropdown_toggler`).click();}
return __exports;});;

/* /web/static/tests/_framework/start.hoot.js */
odoo.define('@web/../tests/_framework/start.hoot',['@odoo/hoot','@web/../tests/_framework/module_set.hoot'],function(require){'use strict';let __exports={};const{definePreset,defineTags,isHootReady}=require("@odoo/hoot");const{runTests}=require("@web/../tests/_framework/module_set.hoot");function beforeFocusRequired(test){if(!document.hasFocus()){console.warn("[FOCUS REQUIRED]",`test "${test.name}" requires focus inside of the browser window and will probably fail without it`);}}
definePreset("desktop",{icon:"fa-desktop",label:"Desktop",size:[1366,768],tags:["-mobile"],touch:false,});definePreset("mobile",{icon:"fa-mobile font-bold",label:"Mobile",size:[375,667],tags:["-desktop"],touch:true,});defineTags({name:"desktop",exclude:["headless","mobile"],},{name:"mobile",exclude:["desktop","headless"],},{name:"headless",exclude:["desktop","mobile"],},{name:"focus required",before:beforeFocusRequired,});isHootReady.then(()=>runTests({fileSuffix:".test"}));return __exports;});;

/* /web/static/tests/_framework/touch_helpers.js */
odoo.define('@web/../tests/_framework/touch_helpers',['@odoo/hoot','@web/../tests/_framework/dom_test_helpers'],function(require){'use strict';let __exports={};const{advanceTime,queryFirst}=require("@odoo/hoot");const{contains}=require("@web/../tests/_framework/dom_test_helpers");async function swipe(target,direction,dragOptions,moveToOptions){const el=queryFirst(target);if(direction<0){el.scrollLeft=el.scrollWidth-el.offsetWidth;}else{el.scrollLeft=0;}
const{moveTo,drop}=await contains(el).drag({position:{x:0,y:0},...dragOptions,});await moveTo(el,{position:{x:direction*el.clientWidth},...moveToOptions,});await drop();await advanceTime(1000);}
__exports.swipeLeft=swipeLeft;async function swipeLeft(target,dragOptions,moveToOptions){await swipe(target,-1,dragOptions,moveToOptions);}
__exports.swipeRight=swipeRight;async function swipeRight(target,dragOptions,moveToOptions){await swipe(target,+1,dragOptions,moveToOptions);}
return __exports;});;

/* /web/static/tests/_framework/translation_test_helpers.js */
odoo.define('@web/../tests/_framework/translation_test_helpers',['@odoo/hoot','@web/../tests/_framework/mock_server_state.hoot','@web/../tests/_framework/patch_test_helpers','@web/core/l10n/translation'],function(require){'use strict';let __exports={};const{after}=require("@odoo/hoot");const{serverState}=require("@web/../tests/_framework/mock_server_state.hoot");const{patchWithCleanup}=require("@web/../tests/_framework/patch_test_helpers");const{loadLanguages,translatedTerms,translationLoaded}=require("@web/core/l10n/translation");__exports.installLanguages=installLanguages;function installLanguages(languages){serverState.multiLang=true;patchWithCleanup(loadLanguages,{installedLanguages:Object.entries(languages),});}
__exports.patchTranslations=patchTranslations;function patchTranslations(terms={}){translatedTerms[translationLoaded]=true;after(()=>{translatedTerms[translationLoaded]=false;});patchWithCleanup(translatedTerms,terms);}
return __exports;});;

/* /web/static/tests/_framework/view_test_helpers.js */
odoo.define('@web/../tests/_framework/view_test_helpers',['@odoo/hoot','@odoo/owl','@web/core/dialog/dialog','@web/core/main_components_container','@web/views/view','@web/../tests/_framework/component_test_helpers','@web/../tests/_framework/dom_test_helpers','@web/../tests/_framework/env_test_helpers','@web/../tests/_framework/mock_server/mock_model'],function(require){'use strict';let __exports={};const{after,animationFrame,click,Deferred,expect,formatXml,getFixture,queryAll,queryAllTexts,tick,}=require("@odoo/hoot");const{Component,onMounted,useSubEnv,xml}=require("@odoo/owl");const{Dialog}=require("@web/core/dialog/dialog");const{MainComponentsContainer}=require("@web/core/main_components_container");const{View}=require("@web/views/view");const{mountWithCleanup}=require("@web/../tests/_framework/component_test_helpers");const{contains}=require("@web/../tests/_framework/dom_test_helpers");const{getService}=require("@web/../tests/_framework/env_test_helpers");const{registerInlineViewArchs}=require("@web/../tests/_framework/mock_server/mock_model");const isNil=(value)=>value===null||value===undefined;class ViewDialog extends Component{static components={Dialog,View};static props={onMounted:Function,viewEnv:Object,viewProps:Object,close:Function,};static template=xml`
        <Dialog>
            <View t-props="props.viewProps" />
        </Dialog>
    `;setup(){useSubEnv(this.props.viewEnv);onMounted(()=>this.props.onMounted());}}
__exports.buildSelector=buildSelector;function buildSelector(base,params){let selector=base;params||={};if(params.id){selector+=`#${params.id}`;}
if(params.class){selector+=`.${params.class}`;}
if(params.modifier){selector+=`:${params.modifier}`;}
if(params.text){selector+=`:contains(${params.text})`;}
if(!isNil(params.index)){selector+=`:eq(${params.index})`;}
if(params.target){selector+=` ${params.target}`;}
return selector;}
__exports.clickButton=clickButton;async function clickButton(options){await contains(buildSelector(`.btn:enabled`,options)).click();}
__exports.clickCancel=clickCancel;async function clickCancel(options){await contains(buildSelector(`.o_form_button_cancel:enabled`,options)).click();}
__exports.clickFieldDropdown=clickFieldDropdown;async function clickFieldDropdown(fieldName,options){await contains(buildSelector(`[name='${fieldName}'] .dropdown input`,options)).click();}
__exports.clickFieldDropdownItem=clickFieldDropdownItem;async function clickFieldDropdownItem(fieldName,itemContent,options){const dropdowns=queryAll(buildSelector(`[name='${fieldName}'] .dropdown .dropdown-menu`,options));if(dropdowns.length===0){throw new Error(`No dropdown found for field ${fieldName}`);}else if(dropdowns.length>1){throw new Error(`Found ${dropdowns.length} dropdowns for field ${fieldName}`);}
const dropdownItems=queryAll(buildSelector("li",options),{root:dropdowns[0]});const indexToClick=queryAllTexts(dropdownItems).indexOf(itemContent);if(indexToClick===-1){throw new Error(`The element '${itemContent}' does not exist in the dropdown`);}
await click(dropdownItems[indexToClick]);await animationFrame();}
__exports.clickModalButton=clickModalButton;async function clickModalButton(options){await contains(buildSelector(`.modal .btn:enabled`,options)).click();}
__exports.clickSave=clickSave;async function clickSave(options){await contains(buildSelector(`.o_form_button_save:enabled`,options)).click();}
__exports.clickViewButton=clickViewButton;async function clickViewButton(options){await contains(buildSelector(`.o_view_controller .btn:enabled`,options)).click();}
__exports.expectMarkup=expectMarkup;function expectMarkup(value){return{toBe(expected,options){expect(formatXml(value,options)).toBe(formatXml(expected,options));},};}
__exports.fieldInput=fieldInput;function fieldInput(name,options){return contains(buildSelector(`.o_field_widget[name='${name}'] input`,options));}
__exports.mountViewInDialog=mountViewInDialog;async function mountViewInDialog(params){const container=await mountWithCleanup(MainComponentsContainer,{env:params.env,});const deferred=new Deferred();getService("dialog").add(ViewDialog,{viewEnv:{config:params.config},viewProps:parseViewProps(params),onMounted(){deferred.resolve();},});await deferred;return container;}
__exports.mountView=mountView;async function mountView(params,target=null){const actionManagerEl=document.createElement("div");actionManagerEl.classList.add("o_action_manager");(target??getFixture()).append(actionManagerEl);after(()=>actionManagerEl.remove());return mountWithCleanup(View,{env:params.env,componentEnv:{config:params.config},props:parseViewProps(params),target:actionManagerEl,});}
__exports.parseViewProps=parseViewProps;function parseViewProps(props){let className="o_action";if(props.className){className+=" "+props.className;}
const viewProps={...props,className};if(props.archs||!isNil(props.arch)||!isNil(props.searchViewArch)||!isNil(props.searchViewId)||!isNil(props.viewId)){viewProps.viewId??=-1;viewProps.searchViewId??=-1;registerInlineViewArchs(viewProps.resModel,{...props.archs,[[viewProps.type,viewProps.viewId]]:viewProps.arch,[["search",viewProps.searchViewId]]:viewProps.searchViewArch,});}else{viewProps.viewId=false;viewProps.searchViewId=false;}
delete viewProps.arch;delete viewProps.archs;delete viewProps.config;delete viewProps.searchViewArch;return viewProps;}
__exports.selectFieldDropdownItem=selectFieldDropdownItem;async function selectFieldDropdownItem(fieldName,itemContent,options){await clickFieldDropdown(fieldName,options);await clickFieldDropdownItem(fieldName,itemContent);}
__exports.hideTab=hideTab;async function hideTab(){const prop=Object.getOwnPropertyDescriptor(Document.prototype,"visibilityState");Object.defineProperty(document,"visibilityState",{value:"hidden",configurable:true,writable:true,});document.dispatchEvent(new Event("visibilitychange"));await tick();Object.defineProperty(document,"visibilityState",prop);}
return __exports;});;

/* /web/static/tests/_framework/webclient_test_helpers.js */
odoo.define('@web/../tests/_framework/webclient_test_helpers',['@odoo/hoot','@odoo/owl','@web/core/registry','@web/webclient/webclient','@web/../tests/_framework/component_test_helpers'],function(require){'use strict';let __exports={};const{animationFrame}=require("@odoo/hoot");const{Component,xml}=require("@odoo/owl");const{registry}=require("@web/core/registry");const{WebClient}=require("@web/webclient/webclient");const{mountWithCleanup}=require("@web/../tests/_framework/component_test_helpers");class TestClientAction extends Component{static template=xml`
        <div class="test_client_action">
            ClientAction_<t t-esc="props.action.params?.description"/>
        </div>`;static props=["*"];}
__exports.useTestClientAction=useTestClientAction;function useTestClientAction(){const tag="__test__client__action__";registry.category("actions").add(tag,TestClientAction);return{tag,target:"main",type:"ir.actions.client",params:{description:"Id 1"},};}
__exports.mountWebClient=mountWebClient;async function mountWebClient(options){await mountWithCleanup(WebClient,options);await animationFrame();await animationFrame();await animationFrame();}
return __exports;});;

/* /im_livechat/static/tests/embed/autopopup.test.js */
odoo.define('@im_livechat/../tests/embed/autopopup.test',['@im_livechat/embed/common/expirable_storage','@im_livechat/../tests/livechat_test_helpers','@odoo/hoot','@mail/../tests/mail_test_helpers','@web/../tests/web_test_helpers'],function(require){'use strict';let __exports={};const{expirableStorage}=require("@im_livechat/embed/common/expirable_storage");const{defineLivechatModels,loadDefaultEmbedConfig,}=require("@im_livechat/../tests/livechat_test_helpers");const{describe,test}=require("@odoo/hoot");const{contains,start,startServer}=require("@mail/../tests/mail_test_helpers");const{Command,onRpc,serverState}=require("@web/../tests/web_test_helpers");describe.current.tags("desktop");defineLivechatModels();test("persisted session",async()=>{const pyEnv=await startServer();const livechatChannelId=await loadDefaultEmbedConfig();const guestId=pyEnv["mail.guest"].create({name:"Visitor 11"});const channelId=pyEnv["discuss.channel"].create({channel_member_ids:[Command.create({partner_id:serverState.partnerId}),Command.create({guest_id:guestId,fold_state:"open"}),],channel_type:"livechat",livechat_active:true,livechat_channel_id:livechatChannelId,livechat_operator_id:serverState.partnerId,});expirableStorage.setItem("im_livechat.saved_state",JSON.stringify({store:{"discuss.channel":[{id:channelId}]},persisted:true,livechatUserId:serverState.publicUserId,}));await start({authenticateAs:{...pyEnv["mail.guest"].read(guestId)[0],_name:"mail.guest"},});await contains(".o-mail-ChatWindow");});test("rule received in init",async()=>{await startServer();await loadDefaultEmbedConfig();onRpc("/im_livechat/init",()=>{return{available_for_me:true,rule:{action:"auto_popup",auto_popup_delay:0},};});await start({authenticateAs:false});await contains(".o-mail-ChatWindow");});return __exports;});;

/* /im_livechat/static/tests/embed/chat_window.test.js */
odoo.define('@im_livechat/../tests/embed/chat_window.test',['@im_livechat/embed/common/livechat_button','@im_livechat/../tests/livechat_test_helpers','@odoo/hoot','@web/core/l10n/dates','@web/core/utils/urls','@web/../tests/web_test_helpers','@mail/../tests/mail_test_helpers','@im_livechat/embed/common/expirable_storage'],function(require){'use strict';let __exports={};const{LivechatButton}=require("@im_livechat/embed/common/livechat_button");const{defineLivechatModels,loadDefaultEmbedConfig,}=require("@im_livechat/../tests/livechat_test_helpers");const{describe,test}=require("@odoo/hoot");const{deserializeDateTime}=require("@web/core/l10n/dates");const{getOrigin}=require("@web/core/utils/urls");const{Command,mountWithCleanup,patchWithCleanup,serverState,}=require("@web/../tests/web_test_helpers");const{assertSteps,click,contains,inputFiles,insertText,onRpcBefore,patchUiSize,SIZES,start,startServer,step,triggerHotkey,}=require("@mail/../tests/mail_test_helpers");const{expirableStorage}=require("@im_livechat/embed/common/expirable_storage");describe.current.tags("desktop");defineLivechatModels();test("do not save fold state of temporary live chats",async()=>{patchWithCleanup(LivechatButton,{DEBOUNCE_DELAY:0});await startServer();await loadDefaultEmbedConfig();onRpcBefore("/discuss/channel/fold",(args)=>{step(`fold - ${args.state}`);});await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await contains(".o-mail-Message",{text:"Hello, how may I help you?"});await assertSteps([]);await insertText(".o-mail-Composer-input","Hello");await triggerHotkey("Enter");await contains(".o-mail-Message",{text:"Hello"});await click(".o-mail-ChatWindow-header");await contains(".o-mail-Message",{text:"Hello",count:0});await assertSteps(["fold - folded"]);await click(".o-mail-ChatBubble");await click("[title*='Close Chat Window']");await assertSteps(["fold - open"]);await click(".o-livechat-CloseConfirmation-leave");await click("button",{text:"Close conversation"});await assertSteps(["fold - closed"]);await click(".o-livechat-LivechatButton");await contains(".o-mail-Message",{text:"Hello, how may I help you?"});await assertSteps([]);await click(".o-mail-ChatWindow-header");await assertSteps([]);});test("internal users can upload file to temporary thread",async()=>{const pyEnv=await startServer();await loadDefaultEmbedConfig();const[partnerUser]=pyEnv["res.users"].search_read([["id","=",serverState.partnerId]]);await start({authenticateAs:partnerUser});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");const file=new File(["hello, world"],"text.txt",{type:"text/plain"});await contains(".o-mail-Composer");await contains("button[title='Attach files']");await inputFiles(".o-mail-Composer-coreMain .o_input_file",[file]);await contains(".o-mail-AttachmentCard:not(.o-isUploading):contains(text.txt) .fa-check");await triggerHotkey("Enter");await contains(".o-mail-Message .o-mail-AttachmentCard:contains(text.txt)");});test("Conversation name is operator livechat user name",async()=>{const pyEnv=await startServer();await loadDefaultEmbedConfig();pyEnv["res.partner"].write(serverState.partnerId,{user_livechat_username:"MitchellOp"});await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await contains(".o-mail-ChatWindow-header",{text:"MitchellOp"});});test("avatar url contains access token for non-internal users",async()=>{const pyEnv=await startServer();await loadDefaultEmbedConfig();pyEnv["res.partner"].write(serverState.partnerId,{user_livechat_username:"MitchellOp"});const[partner]=pyEnv["res.partner"].search_read([["id","=",serverState.partnerId]]);await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await contains(`.o-mail-ChatWindow-threadAvatar img[data-src="${getOrigin()}/web/image/res.partner/${
            partner.id
        }/avatar_128?access_token=${partner.id}&unique=${
            deserializeDateTime(partner.write_date).ts
        }"]`);await contains(`.o-mail-Message-avatar[data-src="${getOrigin()}/web/image/res.partner/${
            partner.id
        }/avatar_128?access_token=${partner.id}&unique=${
            deserializeDateTime(partner.write_date).ts
        }"]`);await insertText(".o-mail-Composer-input","Hello World!");triggerHotkey("Enter");const guestId=pyEnv.cookie.get("dgid");const[guest]=pyEnv["mail.guest"].read(guestId);await contains(`.o-mail-Message-avatar[data-src="${getOrigin()}/web/image/mail.guest/${
            guest.id
        }/avatar_128?access_token=${guest.id}&unique=${deserializeDateTime(guest.write_date).ts}"]`);});test("livechat is shown as bubble on page reload",async()=>{const pyEnv=await startServer();const livechatChannelId=await loadDefaultEmbedConfig();const guestId=pyEnv["mail.guest"].create({name:"Visitor 11"});const channelId=pyEnv["discuss.channel"].create({channel_member_ids:[Command.create({partner_id:serverState.partnerId}),Command.create({guest_id:guestId,fold_state:"open"}),],channel_type:"livechat",livechat_active:true,livechat_channel_id:livechatChannelId,livechat_operator_id:serverState.partnerId,});expirableStorage.setItem("im_livechat.saved_state",JSON.stringify({store:{"discuss.channel":[{id:channelId}]},persisted:true,livechatUserId:serverState.publicUserId,}));pyEnv["res.partner"].write(serverState.partnerId,{user_livechat_username:"MitchellOp"});patchUiSize({size:SIZES.SM});await start({authenticateAs:{...pyEnv["mail.guest"].read(guestId)[0],_name:"mail.guest"},});await click(".o-mail-ChatBubble");await contains(".o-mail-ChatWindow-header",{text:"MitchellOp"});});return __exports;});;

/* /im_livechat/static/tests/embed/expirable_storage.test.js */
odoo.define('@im_livechat/../tests/embed/expirable_storage.test',['@im_livechat/embed/common/expirable_storage','@odoo/hoot','@odoo/hoot-mock'],function(require){'use strict';let __exports={};const{expirableStorage}=require("@im_livechat/embed/common/expirable_storage");const{describe,expect,test}=require("@odoo/hoot");const{mockDate}=require("@odoo/hoot-mock");describe.current.tags("desktop");test("value is removed from expirable storage after expiration",async()=>{mockDate("2023-01-01 00:00:00");const ONE_DAY=60*60*24;expirableStorage.setItem("foo","bar",ONE_DAY);expect(expirableStorage.getItem("foo")).toBe("bar");mockDate("2023-01-01 23:00:00");expect(expirableStorage.getItem("foo")).toBe("bar");mockDate("2023-01-02 00:00:01");expect(expirableStorage.getItem("foo")).toBe(null);});return __exports;});;

/* /im_livechat/static/tests/embed/feedback_panel.test.js */
odoo.define('@im_livechat/../tests/embed/feedback_panel.test',['@im_livechat/../tests/livechat_test_helpers','@im_livechat/embed/common/livechat_button','@im_livechat/embed/common/livechat_service','@mail/../tests/mail_test_helpers','@odoo/hoot','@web/../tests/web_test_helpers'],function(require){'use strict';let __exports={};const{defineLivechatModels,loadDefaultEmbedConfig,}=require("@im_livechat/../tests/livechat_test_helpers");const{LivechatButton}=require("@im_livechat/embed/common/livechat_button");const{RATING}=require("@im_livechat/embed/common/livechat_service");const{assertSteps,click,contains,insertText,onRpcBefore,start,startServer,step,triggerHotkey,}=require("@mail/../tests/mail_test_helpers");const{describe,expect,test}=require("@odoo/hoot");const{mountWithCleanup}=require("@web/../tests/web_test_helpers");describe.current.tags("desktop");defineLivechatModels();test("Do not ask feedback if empty",async()=>{await startServer();await loadDefaultEmbedConfig();await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await contains(".o-mail-ChatWindow");await click("[title*='Close Chat Window']");});test("Close without feedback",async()=>{await startServer();await loadDefaultEmbedConfig();onRpcBefore((route)=>{if(route==="/im_livechat/visitor_leave_session"){step(route);}
if(route==="/im_livechat/feedback"){step(route);}});await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await contains(".o-mail-ChatWindow");await insertText(".o-mail-Composer-input","Hello World!");triggerHotkey("Enter");await contains(".o-mail-Message-content",{text:"Hello World!"});await click("[title*='Close Chat Window']");await click(".o-livechat-CloseConfirmation-leave");await click("button",{text:"Close conversation"});await contains(".o-livechat-LivechatButton");await assertSteps(["/im_livechat/visitor_leave_session"]);});test("Feedback with rating and comment",async()=>{await startServer();await loadDefaultEmbedConfig();onRpcBefore((route,args)=>{if(route==="/im_livechat/visitor_leave_session"){step(route);}
if(route==="/im_livechat/feedback"){step(route);expect(args.reason).toInclude("Good job!");expect(args.rate).toBe(RATING.GOOD);}});await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await contains(".o-mail-ChatWindow");await insertText(".o-mail-Composer-input","Hello World!");triggerHotkey("Enter");await contains(".o-mail-Message-content",{text:"Hello World!"});await click("[title*='Close Chat Window']");await click(".o-livechat-CloseConfirmation-leave");await assertSteps(["/im_livechat/visitor_leave_session"]);await click(`img[alt="${RATING.GOOD}"]`);await insertText("textarea[placeholder='Explain your note']","Good job!");await click("button:contains(Send):enabled");await contains("p",{text:"Thank you for your feedback"});await assertSteps(["/im_livechat/feedback"]);});test("Closing folded chat window should open it with feedback",async()=>{await startServer();await loadDefaultEmbedConfig();await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await insertText(".o-mail-Composer-input","Hello World!");triggerHotkey("Enter");await contains(".o-mail-Message-content",{text:"Hello World!"});await click("[title='Fold']");await click(".o-mail-ChatBubble");await click("[title*='Close Chat Window']");await click(".o-livechat-CloseConfirmation-leave");await click(".o-mail-ChatHub-bubbleBtn");await contains(".o-mail-ChatWindow p",{text:"Did we correctly answer your question?"});});return __exports;});;

/* /im_livechat/static/tests/embed/history_command.test.js */
odoo.define('@im_livechat/../tests/embed/history_command.test',['@im_livechat/../tests/livechat_test_helpers','@im_livechat/embed/common/livechat_button','@mail/../tests/mail_test_helpers','@odoo/hoot','@odoo/hoot-mock','@web/../tests/web_test_helpers'],function(require){'use strict';let __exports={};const{defineLivechatModels,loadDefaultEmbedConfig,}=require("@im_livechat/../tests/livechat_test_helpers");const{LivechatButton}=require("@im_livechat/embed/common/livechat_button");const{assertSteps,click,contains,insertText,start,startServer,step,triggerHotkey,}=require("@mail/../tests/mail_test_helpers");const{describe,test}=require("@odoo/hoot");const{tick}=require("@odoo/hoot-mock");const{getService,mountWithCleanup,onRpc}=require("@web/../tests/web_test_helpers");describe.current.tags("desktop");defineLivechatModels();test("Handle livechat history command",async()=>{const pyEnv=await startServer();await loadDefaultEmbedConfig();onRpc("/im_livechat/history",()=>{step("/im_livechat/history");return true;});await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await insertText(".o-mail-Composer-input","Hello World!");triggerHotkey("Enter");await contains(".o-mail-Message",{count:2});const thread=getService("im_livechat.livechat").thread;const guestId=pyEnv.cookie.get("dgid");const[guest]=pyEnv["mail.guest"].read(guestId);pyEnv["bus.bus"]._sendone(guest,"im_livechat.history_command",{id:thread.id,});await tick();await assertSteps(["/im_livechat/history"]);});return __exports;});;

/* /im_livechat/static/tests/embed/livechat_button.test.js */
odoo.define('@im_livechat/../tests/embed/livechat_button.test',['@im_livechat/embed/common/livechat_button','@im_livechat/../tests/livechat_test_helpers','@odoo/hoot','@mail/../tests/mail_test_helpers','@web/../tests/web_test_helpers'],function(require){'use strict';let __exports={};const{LivechatButton}=require("@im_livechat/embed/common/livechat_button");const{defineLivechatModels,loadDefaultEmbedConfig,}=require("@im_livechat/../tests/livechat_test_helpers");const{describe,expect,test}=require("@odoo/hoot");const{assertSteps,click,contains,insertText,start,startServer,step,triggerHotkey,}=require("@mail/../tests/mail_test_helpers");const{asyncStep,mountWithCleanup,onRpc}=require("@web/../tests/web_test_helpers");describe.current.tags("desktop");defineLivechatModels();test("open/close temporary channel",async()=>{await startServer();await loadDefaultEmbedConfig();const env=await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await contains(".o-mail-ChatWindow");await contains(".o-livechat-LivechatButton",{count:0});expect(Boolean(env.services["im_livechat.livechat"].thread)).toBe(true);await click("[title*='Close Chat Window']");await contains(".o-mail-ChatWindow",{count:0});expect(Boolean(env.services["im_livechat.livechat"].thread)).toBe(false);await contains(".o-livechat-LivechatButton",{count:1});});test("open/close persisted channel",async()=>{await startServer();await loadDefaultEmbedConfig();onRpc("/im_livechat/get_session",async(req)=>{const{params}=await req.json();if(params.persisted){step("persisted");}});const env=await start({authenticateAs:false});env.services.bus_service.subscribe("discuss.channel/new_message",()=>asyncStep("discuss.channel/new_message"));await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await insertText(".o-mail-Composer-input","How can I help?");await triggerHotkey("Enter");await assertSteps(["persisted"]);await contains(".o-mail-Message-content",{text:"How can I help?"});await assertSteps(["discuss.channel/new_message"]);await click("[title*='Close Chat Window']");await click(".o-livechat-CloseConfirmation-leave");await contains(".o-mail-ChatWindow",{text:"Did we correctly answer your question?"});await click("[title*='Close Chat Window']");await contains(".o-mail-ChatWindow",{count:0});await contains(".o-livechat-LivechatButton",{count:1});});test("livechat not available",async()=>{await startServer();await loadDefaultEmbedConfig();onRpc("/im_livechat/init",()=>({available_for_me:false}));await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await contains(".o-mail-ChatHub");await contains(".o-livechat-LivechatButton",{count:0});});test("clicking on notification opens the chat",async()=>{await startServer();await loadDefaultEmbedConfig();onRpc("/im_livechat/init",()=>({available_for_me:true,rule:{action:"display_button_and_text"},}));await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton-notification",{text:"Have a Question? Chat with us.",});await contains(".o-mail-ChatWindow");});return __exports;});;

/* /im_livechat/static/tests/embed/livechat_service.test.js */
odoo.define('@im_livechat/../tests/embed/livechat_service.test',['@im_livechat/embed/common/expirable_storage','@im_livechat/embed/common/livechat_button','@im_livechat/../tests/livechat_test_helpers','@odoo/hoot','@mail/../tests/mail_test_helpers','@web/../tests/web_test_helpers'],function(require){'use strict';let __exports={};const{expirableStorage}=require("@im_livechat/embed/common/expirable_storage");const{LivechatButton}=require("@im_livechat/embed/common/livechat_button");const{defineLivechatModels,loadDefaultEmbedConfig,}=require("@im_livechat/../tests/livechat_test_helpers");const{describe,test}=require("@odoo/hoot");const{assertSteps,click,contains,insertText,onRpcBefore,start,startServer,step,triggerHotkey,}=require("@mail/../tests/mail_test_helpers");const{Command,mountWithCleanup,serverState}=require("@web/../tests/web_test_helpers");describe.current.tags("desktop");defineLivechatModels();test("persisted session history",async()=>{const pyEnv=await startServer();const livechatChannelId=await loadDefaultEmbedConfig();const guestId=pyEnv["mail.guest"].create({name:"Visitor 11"});const channelId=pyEnv["discuss.channel"].create({channel_member_ids:[Command.create({partner_id:serverState.partnerId}),Command.create({guest_id:guestId,fold_state:"open"}),],livechat_active:true,channel_type:"livechat",livechat_channel_id:livechatChannelId,livechat_operator_id:serverState.partnerId,});expirableStorage.setItem("im_livechat.saved_state",JSON.stringify({store:{"discuss.channel":[{id:channelId}]},persisted:true,livechatUserId:serverState.publicUserId,}));pyEnv["mail.message"].create({author_id:serverState.partnerId,body:"Old message in history",res_id:channelId,model:"discuss.channel",message_type:"comment",});await start({authenticateAs:{...pyEnv["mail.guest"].read(guestId)[0],_name:"mail.guest"},});await mountWithCleanup(LivechatButton);await contains(".o-mail-Message-content",{text:"Old message in history"});});test("previous operator prioritized",async()=>{const pyEnv=await startServer();const livechatChannelId=await loadDefaultEmbedConfig();const userId=pyEnv["res.users"].create({name:"John Doe",im_status:"online"});const previousOperatorId=pyEnv["res.partner"].create({name:"John Doe",user_ids:[userId],});pyEnv["im_livechat.channel"].write([livechatChannelId],{user_ids:[Command.link(userId)]});expirableStorage.setItem("im_livechat_previous_operator",JSON.stringify(previousOperatorId));await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await contains(".o-mail-Message-author",{text:"John Doe"});});test("Only necessary requests are made when creating a new chat",async()=>{const pyEnv=await startServer();const livechatChannelId=await loadDefaultEmbedConfig();const operatorPartnerId=serverState.partnerId;onRpcBefore((route,args)=>{if(!route.includes("assets")){step(`${route} - ${JSON.stringify(args)}`);}});await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await contains(".o-livechat-LivechatButton");await assertSteps([`/im_livechat/init - {"channel_id":${livechatChannelId}}`]);await click(".o-livechat-LivechatButton");await contains(".o-mail-Message",{text:"Hello, how may I help you?"});await assertSteps([`/im_livechat/get_session - ${JSON.stringify({
            channel_id: livechatChannelId,
            anonymous_name: "Visitor",
            previous_operator_id: null,
            persisted: false,
        })}`,]);await insertText(".o-mail-Composer-input","Hello!");await assertSteps([]);await triggerHotkey("Enter");await contains(".o-mail-Message",{text:"Hello!"});const[threadId]=pyEnv["discuss.channel"].search([],{order:"id DESC"});await assertSteps([`/im_livechat/get_session - ${JSON.stringify({
            channel_id: livechatChannelId,
            anonymous_name: "Visitor",
            previous_operator_id: operatorPartnerId,
            temporary_id: -1,
            persisted: true,
        })}`,`/mail/data - ${JSON.stringify({
            init_messaging: {
                channel_types: ["livechat"],
            },
            failures: true, // called because mail/core/web is loaded in test bundle
            systray_get_activities: true, // called because mail/core/web is loaded in test bundle
            context: { lang: "en", tz: "taht", uid: serverState.userId, allowed_company_ids: [1] },
        })}`,`/mail/message/post - ${JSON.stringify({
            context: {
                lang: "en",
                tz: "taht",
                uid: serverState.userId,
                allowed_company_ids: [1],
                temporary_id: 0.81,
            },
            post_data: {
                body: "Hello!",
                email_add_signature: true,
                message_type: "comment",
                subtype_xmlid: "mail.mt_comment",
            },
            thread_id: threadId,
            thread_model: "discuss.channel",
        })}`,]);});return __exports;});;

/* /im_livechat/static/tests/embed/livechat_session.test.js */
odoo.define('@im_livechat/../tests/embed/livechat_session.test',['@bus/../tests/bus_test_helpers','@im_livechat/../tests/livechat_test_helpers','@mail/../tests/mail_test_helpers','@odoo/hoot','@odoo/hoot-mock','@web/../tests/web_test_helpers','@im_livechat/embed/common/livechat_button','@web/core/network/rpc','@odoo/hoot-dom'],function(require){'use strict';let __exports={};const{waitUntilSubscribe}=require("@bus/../tests/bus_test_helpers");const{defineLivechatModels,loadDefaultEmbedConfig,}=require("@im_livechat/../tests/livechat_test_helpers");const{click,contains,focus,insertText,onRpcBefore,start,startServer,triggerHotkey,}=require("@mail/../tests/mail_test_helpers");const{describe,expect,test}=require("@odoo/hoot");const{advanceTime}=require("@odoo/hoot-mock");const{getService,mountWithCleanup,serverState,withUser,}=require("@web/../tests/web_test_helpers");const{LivechatButton}=require("@im_livechat/embed/common/livechat_button");const{rpc}=require("@web/core/network/rpc");const{queryFirst}=require("@odoo/hoot-dom");describe.current.tags("desktop");defineLivechatModels();test("Session is reset after failing to persist the channel",async()=>{await startServer();await loadDefaultEmbedConfig();onRpcBefore("/im_livechat/get_session",(args)=>{if(args.persisted){return false;}});await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await insertText(".o-mail-Composer-input","Hello World!");triggerHotkey("Enter");await contains(".o_notification",{text:"No available collaborator, please try again later.",});await contains(".o-livechat-LivechatButton");await advanceTime(LivechatButton.DEBOUNCE_DELAY+10);await click(".o-livechat-LivechatButton");await contains(".o-mail-ChatWindow");});test("Fold state is saved on the server",async()=>{const pyEnv=await startServer();await loadDefaultEmbedConfig();await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await contains(".o-mail-Thread");await insertText(".o-mail-Composer-input","Hello World!");triggerHotkey("Enter");await contains(".o-mail-Message",{text:"Hello World!"});const guestId=pyEnv.cookie.get("dgid");let[member]=pyEnv["discuss.channel.member"].search_read([["guest_id","=",guestId],["channel_id","=",getService("im_livechat.livechat").thread.id],]);expect(member.fold_state).toBe("open");await click(".o-mail-ChatWindow-header");await contains(".o-mail-Thread",{count:0});[member]=pyEnv["discuss.channel.member"].search_read([["guest_id","=",guestId],["channel_id","=",getService("im_livechat.livechat").thread.id],]);expect(member.fold_state).toBe("folded");await click(".o-mail-ChatBubble");});test.tags("focus required");test("Seen message is saved on the server",async()=>{const pyEnv=await startServer();await loadDefaultEmbedConfig();const userId=serverState.userId;await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await contains(".o-mail-Thread");await insertText(".o-mail-Composer-input","Hello, I need help!");triggerHotkey("Enter");await contains(".o-mail-Message",{text:"Hello, I need help!"});await waitUntilSubscribe();const initialSeenMessageId=getService("im_livechat.livechat").thread.selfMember.seen_message_id?.id;queryFirst(".o-mail-Composer-input").blur();await withUser(userId,()=>rpc("/mail/message/post",{post_data:{body:"Hello World!",message_type:"comment",subtype_xmlid:"mail.mt_comment",},thread_id:getService("im_livechat.livechat").thread.id,thread_model:"discuss.channel",}));await contains(".o-mail-Thread-newMessage");await contains(".o-mail-ChatWindow-counter",{text:"1"});await focus(".o-mail-Composer-input");await contains(".o-mail-ChatWindow-counter",{count:0});const guestId=pyEnv.cookie.get("dgid");const[member]=pyEnv["discuss.channel.member"].search_read([["guest_id","=",guestId],["channel_id","=",getService("im_livechat.livechat").thread.id],]);expect(initialSeenMessageId).not.toBe(member.seen_message_id[0]);expect(getService("im_livechat.livechat").thread.selfMember.seen_message_id.id).toBe(member.seen_message_id[0]);});return __exports;});;

/* /im_livechat/static/tests/embed/message_actions.test.js */
odoo.define('@im_livechat/../tests/embed/message_actions.test',['@im_livechat/embed/common/livechat_button','@im_livechat/../tests/livechat_test_helpers','@odoo/hoot','@mail/../tests/mail_test_helpers','@web/../tests/web_test_helpers'],function(require){'use strict';let __exports={};const{LivechatButton}=require("@im_livechat/embed/common/livechat_button");const{defineLivechatModels,loadDefaultEmbedConfig,}=require("@im_livechat/../tests/livechat_test_helpers");const{describe,test}=require("@odoo/hoot");const{click,contains,insertText,start,startServer,triggerHotkey,}=require("@mail/../tests/mail_test_helpers");const{asyncStep,mountWithCleanup,waitForSteps}=require("@web/../tests/web_test_helpers");describe.current.tags("desktop");defineLivechatModels();test("Only two quick actions are shown",async()=>{await startServer();await loadDefaultEmbedConfig();const env=await start({authenticateAs:false});env.services.bus_service.subscribe("discuss.channel/new_message",()=>asyncStep("discuss.channel/new_message"));await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await contains(".o-mail-ChatWindow");await insertText(".o-mail-Composer-input","Hello World!");triggerHotkey("Enter");await waitForSteps(["discuss.channel/new_message"]);await click("[title='Add a Reaction']");await click(".o-Emoji",{text:"😅"});await contains(".o-mail-MessageReaction",{text:"😅"});await contains(".o-mail-Message-actions i",{count:3});await contains("[title='Add a Reaction']");await contains("[title='Reply']");await contains("[title='Expand']");await click("[title='Expand']");await contains(".o-mail-Message-actions i, .o-mail-Message-moreMenu i",{count:7});await contains("[title='Copy Link']");await contains("[title='Edit']");await contains("[title='Delete']");await contains("[title='View Reactions']");});return __exports;});;

/* /im_livechat/static/tests/embed/transcript_sender.test.js */
odoo.define('@im_livechat/../tests/embed/transcript_sender.test',['@im_livechat/embed/common/livechat_button','@im_livechat/../tests/livechat_test_helpers','@odoo/hoot','@mail/../tests/mail_test_helpers','@web/../tests/web_test_helpers'],function(require){'use strict';let __exports={};const{LivechatButton}=require("@im_livechat/embed/common/livechat_button");const{defineLivechatModels,loadDefaultEmbedConfig,}=require("@im_livechat/../tests/livechat_test_helpers");const{describe,test}=require("@odoo/hoot");const{assertSteps,click,contains,insertText,onRpcBefore,start,startServer,step,triggerHotkey,}=require("@mail/../tests/mail_test_helpers");const{mountWithCleanup,onRpc}=require("@web/../tests/web_test_helpers");describe.current.tags("desktop");defineLivechatModels();test("send",async()=>{await startServer();await loadDefaultEmbedConfig();onRpcBefore("/im_livechat/email_livechat_transcript",(args)=>step(`send_transcript - ${args.email}`));await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await insertText(".o-mail-Composer-input","Hello World!");triggerHotkey("Enter");await contains(".o-mail-Message-content",{text:"Hello World!"});await click(".o-mail-ChatWindow-command[title*='Close']");await click(".o-livechat-CloseConfirmation-leave");await contains(".form-text",{text:"Receive a copy of this conversation."});await contains("button[data-action='sendTranscript']:disabled");await insertText("input[placeholder='mail@example.com']","odoobot@odoo.com");await click("button[data-action='sendTranscript']:enabled");await contains(".form-text",{text:"The conversation was sent."});await assertSteps(["send_transcript - odoobot@odoo.com"]);});test("send failed",async()=>{await startServer();await loadDefaultEmbedConfig();onRpc("/im_livechat/email_livechat_transcript",()=>{throw new Error();});await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await insertText(".o-mail-Composer-input","Hello World!");triggerHotkey("Enter");await contains(".o-mail-Message-content",{text:"Hello World!"});await click(".o-mail-ChatWindow-command[title*='Close']");await click(".o-livechat-CloseConfirmation-leave");await insertText("input[placeholder='mail@example.com']","odoobot@odoo.com");await click("button[data-action='sendTranscript']:enabled");await contains(".form-text",{text:"An error occurred. Please try again."});});return __exports;});;

/* /im_livechat/static/tests/embed/unread_messages.test.js */
odoo.define('@im_livechat/../tests/embed/unread_messages.test',['@bus/../tests/bus_test_helpers','@im_livechat/../tests/livechat_test_helpers','@mail/../tests/mail_test_helpers','@odoo/hoot','@web/../tests/web_test_helpers','@im_livechat/embed/common/expirable_storage','@im_livechat/embed/common/livechat_button','@web/core/network/rpc','@odoo/hoot-dom'],function(require){'use strict';let __exports={};const{waitUntilSubscribe}=require("@bus/../tests/bus_test_helpers");const{defineLivechatModels,loadDefaultEmbedConfig,}=require("@im_livechat/../tests/livechat_test_helpers");const{assertSteps,click,contains,focus,insertText,onRpcBefore,start,startServer,step,triggerHotkey,}=require("@mail/../tests/mail_test_helpers");const{describe,test}=require("@odoo/hoot");const{Command,mountWithCleanup,serverState,withUser}=require("@web/../tests/web_test_helpers");const{expirableStorage}=require("@im_livechat/embed/common/expirable_storage");const{LivechatButton}=require("@im_livechat/embed/common/livechat_button");const{rpc}=require("@web/core/network/rpc");const{queryFirst}=require("@odoo/hoot-dom");describe.current.tags("desktop");defineLivechatModels();test("new message from operator displays unread counter",async()=>{const pyEnv=await startServer();const livechatChannelId=await loadDefaultEmbedConfig();const guestId=pyEnv["mail.guest"].create({name:"Visitor 11"});const channelId=pyEnv["discuss.channel"].create({channel_member_ids:[Command.create({partner_id:serverState.partnerId}),Command.create({guest_id:guestId}),],channel_type:"livechat",livechat_active:true,livechat_channel_id:livechatChannelId,livechat_operator_id:serverState.partnerId,});expirableStorage.setItem("im_livechat.saved_state",JSON.stringify({store:{"discuss.channel":[{id:channelId}]},persisted:true,livechatUserId:serverState.publicUserId,}));onRpcBefore("/mail/data",(args)=>{if(args.init_messaging){step(`/mail/data - ${JSON.stringify(args)}`);}});const userId=serverState.userId;await start({authenticateAs:{...pyEnv["mail.guest"].read(guestId)[0],_name:"mail.guest"},});await assertSteps([`/mail/data - ${JSON.stringify({
            init_messaging: {
                channel_types: ["livechat"],
            },
            failures: true, // called because mail/core/web is loaded in qunit bundle
            systray_get_activities: true, // called because mail/core/web is loaded in qunit bundle
            context: { lang: "en", tz: "taht", uid: serverState.userId, allowed_company_ids: [1] },
        })}`,]);withUser(userId,()=>rpc("/mail/message/post",{post_data:{body:"Are you there?",message_type:"comment"},thread_id:channelId,thread_model:"discuss.channel",}));await contains(".o-mail-ChatWindow-counter",{text:"1"});});test.tags("focus required");test("focus on unread livechat marks it as read",async()=>{const pyEnv=await startServer();await loadDefaultEmbedConfig();onRpcBefore("/mail/data",(args)=>{if(args.init_messaging){step(`/mail/data - ${JSON.stringify(args)}`);}});const userId=serverState.userId;await start({authenticateAs:false});await mountWithCleanup(LivechatButton);await click(".o-livechat-LivechatButton");await insertText(".o-mail-Composer-input","Hello World!");await triggerHotkey("Enter");await waitUntilSubscribe();await contains(".o-mail-Message-content",{text:"Hello World!"});await assertSteps([`/mail/data - ${JSON.stringify({
            init_messaging: {
                channel_types: ["livechat"],
            },
            failures: true, // called because mail/core/web is loaded in qunit bundle
            systray_get_activities: true, // called because mail/core/web is loaded in qunit bundle
            context: { lang: "en", tz: "taht", uid: serverState.userId, allowed_company_ids: [1] },
        })}`,]);queryFirst(".o-mail-Composer-input").blur();const[channelId]=pyEnv["discuss.channel"].search([["channel_type","=","livechat"],["channel_member_ids","in",pyEnv["discuss.channel.member"].search([["guest_id","=",pyEnv.cookie.get("dgid")]]),],]);await withUser(userId,()=>rpc("/mail/message/post",{post_data:{body:"Are you there?",message_type:"comment"},thread_id:channelId,thread_model:"discuss.channel",}));await contains(".o-mail-ChatWindow-counter",{text:"1"});await contains(".o-mail-Message",{text:"Are you there?"});await focus(".o-mail-Composer-input");await contains(".o-mail-ChatWindow-counter",{count:0});});return __exports;});;

/* /website_livechat/static/tests/mock_server/mock_models/discuss_channel.js */
odoo.define('@website_livechat/../tests/mock_server/mock_models/discuss_channel',['@im_livechat/../tests/livechat_test_helpers','@web/../tests/web_test_helpers'],function(require){'use strict';let __exports={};const{livechatModels}=require("@im_livechat/../tests/livechat_test_helpers");const{fields}=require("@web/../tests/web_test_helpers");function historyDataToString(history){const formatDateTime=(dateTime)=>{const match=dateTime.match(/.* ([0-9]{2}:[0-9]{2}:)/);return match?match[1].slice(0,-1):"";};return history.map((h)=>`${h[0]} (${formatDateTime(h[1])})`).join(" → ");}
const DiscussChannel=__exports.DiscussChannel=class DiscussChannel extends livechatModels.DiscussChannel{livechat_visitor_id=fields.Many2one({relation:"website.visitor",string:"Visitor"});_to_store(ids,store){const ResCountry=this.env["res.country"];const ResLang=this.env["res.lang"];const ResPartner=this.env["res.partner"];const Website=this.env["website"];const WebsiteVisitor=this.env["website.visitor"];super._to_store(...arguments);const channels=this.browse(ids);for(const channel of channels){if(channel.channel_type==="livechat"&&channel.livechat_visitor_id){const channelInfo={};const[visitor]=WebsiteVisitor.browse(channel.livechat_visitor_id);const[partner]=ResPartner.browse(visitor.partner_id);const[country]=ResCountry.browse(visitor.country_id);const visitorHistoryData=JSON.parse(visitor.history_data||"[]");channelInfo.visitor={country:country?{id:country.id,code:country.code}:false,name:partner?.name||partner?.display_name||visitor.display_name||`Visitor #${visitor.id}`,history:historyDataToString(visitorHistoryData),history_data:visitorHistoryData,id:visitor.id,is_connected:visitor.is_connected,lang_name:visitor.lang_id?ResLang.read(visitor.lang_id)[0].name:false,visitorPartner:visitor.partner_id?{id:visitor.partner_id,type:"partner"}:false,type:"visitor",website_name:visitor.website_id?Website.read(visitor.website_id)[0].name:false,};store.add(this.browse(channel.id),channelInfo);}}}}
return __exports;});;

/* /website_livechat/static/tests/mock_server/mock_models/website.js */
odoo.define('@website_livechat/../tests/mock_server/mock_models/website',['@web/../tests/web_test_helpers'],function(require){'use strict';let __exports={};const{models}=require("@web/../tests/web_test_helpers");const Website=__exports.Website=class Website extends models.ServerModel{_name="website";}
return __exports;});;

/* /website_livechat/static/tests/mock_server/mock_models/website_visitor.js */
odoo.define('@website_livechat/../tests/mock_server/mock_models/website_visitor',['@mail/../tests/mock_server/mail_mock_server','@web/../tests/web_test_helpers'],function(require){'use strict';let __exports={};const{mailDataHelpers}=require("@mail/../tests/mock_server/mail_mock_server");const{Command,fields,models,serverState}=require("@web/../tests/web_test_helpers");const WebsiteVisitor=__exports.WebsiteVisitor=class WebsiteVisitor extends models.ServerModel{_name="website.visitor";country_id=fields.Many2one({relation:"res.country",string:"Country"});history_data=fields.Char();lang_id=fields.Many2one({relation:"res.lang",string:"Language"});name=fields.Char({string:"Name"});partner_id=fields.Many2one({relation:"res.partner",string:"Contact"});action_send_chat_request(ids){const BusBus=this.env["bus.bus"];const DiscussChannel=this.env["discuss.channel"];const MailGuest=this.env["mail.guest"];const ResCountry=this.env["res.country"];const ResPartner=this.env["res.partner"];const visitors=this.browse(ids);for(const visitor of visitors){const country=visitor.country_id?ResCountry.browse(visitor.country_id):undefined;const visitor_name=`${visitor.display_name}${country ? `(${country.name})` : ""}`;const membersToAdd=[Command.create({partner_id:serverState.partnerId})];if(visitor.partner_id){membersToAdd.push(Command.create({partner_id:visitor.partner_id}));}
const livechatId=DiscussChannel.create({anonymous_name:visitor_name,channel_member_ids:membersToAdd,channel_type:"livechat",livechat_operator_id:serverState.partnerId,});if(!visitor.partner_id){const guestId=MailGuest.create({name:`Visitor #${visitor.id}`});DiscussChannel.write([livechatId],{channel_member_ids:[Command.create({guest_id:guestId})],});}
const[partner]=ResPartner.read(serverState.partnerId);BusBus._sendone(partner,"website_livechat.send_chat_request",new mailDataHelpers.Store(DiscussChannel.browse(livechatId)).get_result());}}}
return __exports;});;

/* /website_livechat/static/tests/website_livechat_test_helpers.js */
odoo.define('@website_livechat/../tests/website_livechat_test_helpers',['@im_livechat/../tests/livechat_test_helpers','@web/../tests/web_test_helpers','@website_livechat/../tests/mock_server/mock_models/discuss_channel','@website_livechat/../tests/mock_server/mock_models/website','@website_livechat/../tests/mock_server/mock_models/website_visitor'],function(require){'use strict';let __exports={};const{livechatModels}=require("@im_livechat/../tests/livechat_test_helpers");const{defineModels,defineParams}=require("@web/../tests/web_test_helpers");const{DiscussChannel}=require("@website_livechat/../tests/mock_server/mock_models/discuss_channel");const{Website}=require("@website_livechat/../tests/mock_server/mock_models/website");const{WebsiteVisitor}=require("@website_livechat/../tests/mock_server/mock_models/website_visitor");__exports.defineWebsiteLivechatModels=defineWebsiteLivechatModels;function defineWebsiteLivechatModels(){defineParams({suite:"website_livechat"},"replace");return defineModels(websiteLivechatModels);}
const websiteLivechatModels=__exports.websiteLivechatModels={...livechatModels,DiscussChannel,Website,WebsiteVisitor,};return __exports;});;

/* /website_livechat/static/tests/embed/chat_request.test.js */
odoo.define('@website_livechat/../tests/embed/chat_request.test',['@odoo/hoot','@im_livechat/../tests/livechat_test_helpers','@mail/../tests/mail_test_helpers','@web/../tests/web_test_helpers','@web/session','@website_livechat/../tests/website_livechat_test_helpers'],function(require){'use strict';let __exports={};const{describe,test}=require("@odoo/hoot");const{loadDefaultEmbedConfig}=require("@im_livechat/../tests/livechat_test_helpers");const{contains,start,startServer}=require("@mail/../tests/mail_test_helpers");const{Command,patchWithCleanup,serverState}=require("@web/../tests/web_test_helpers");const{session}=require("@web/session");const{defineWebsiteLivechatModels}=require("@website_livechat/../tests/website_livechat_test_helpers");describe.current.tags("desktop");defineWebsiteLivechatModels();test("chat request opens chat window",async()=>{const pyEnv=await startServer();const livechatId=await loadDefaultEmbedConfig();const guestId=pyEnv["mail.guest"].create({name:"Visitor 11"});const channelId=pyEnv["discuss.channel"].create({channel_member_ids:[Command.create({partner_id:serverState.partnerId}),Command.create({guest_id:guestId,fold_state:"open"}),],channel_type:"livechat",livechat_active:true,livechat_channel_id:livechatId,livechat_operator_id:serverState.partnerId,});const[channel]=pyEnv["discuss.channel"].search_read([["id","=",channelId]]);patchWithCleanup(session.livechatData,{options:{...session.livechatData.options,force_thread:{id:channel.id,model:"discuss.channel"},},});await start({authenticateAs:{...pyEnv["mail.guest"].read(guestId)[0],_name:"mail.guest"},});await contains(".o-mail-ChatWindow");});return __exports;});