001    // Copyright 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry.form;
016    
017    import org.apache.hivemind.ApplicationRuntimeException;
018    import org.apache.hivemind.Location;
019    import org.apache.tapestry.*;
020    import org.apache.tapestry.engine.ILink;
021    import org.apache.tapestry.event.BrowserEvent;
022    import org.apache.tapestry.event.EventTarget;
023    import org.apache.tapestry.internal.event.impl.ComponentEventInvoker;
024    import org.apache.tapestry.listener.ListenerInvoker;
025    import org.apache.tapestry.services.ResponseBuilder;
026    import org.apache.tapestry.util.IdAllocator;
027    import org.apache.tapestry.valid.IValidationDelegate;
028    import org.easymock.EasyMock;
029    import static org.easymock.EasyMock.*;
030    import org.easymock.IAnswer;
031    import org.testng.annotations.Test;
032    
033    import java.util.HashMap;
034    import java.util.Map;
035    
036    /**
037     * Tests for {@link org.apache.tapestry.form.FormSupportImpl}.
038     *
039     * @author Howard M. Lewis Ship
040     * @since 4.0
041     */
042    @Test(sequential = true)
043    public class FormSupportTest extends BaseComponentTestCase {
044        
045        protected FormSupport newFormSupport(IMarkupWriter writer, IRequestCycle cycle, IForm form)
046        {
047            return new FormSupportImpl(writer, cycle, form);
048        }
049    
050        private IRender newComponentRenderBody(final FormSupport fs, final IFormComponent component,
051                                               final IMarkupWriter nested)
052        {
053            return newComponentsRenderBody(fs, new IFormComponent[] {component}, nested);
054        }
055    
056        private IRender newComponentsRenderBody(final FormSupport fs, final IFormComponent[] component,
057                                                final IMarkupWriter nested)
058        {
059            return new IRender() {
060                public void render(IMarkupWriter writer, IRequestCycle cycle)
061                {
062                    assertEquals(nested, writer);
063    
064                    for (int i = 0; i < component.length; i++)
065                        fs.getElementId(component[i]);
066                }
067            };
068        }
069    
070        private IValidationDelegate newDelegate()
071        {
072            return newMock(IValidationDelegate.class);
073        }
074    
075        protected IEngine newEngine()
076        {
077            return newMock(IEngine.class);
078        }
079    
080        private IFormComponent newField()
081        {
082            return newMock(IFormComponent.class);
083        }
084    
085        private IFormComponent newFormComponent(String id, String name)
086        {
087            IFormComponent component = newMock(IFormComponent.class);
088            checkOrder(component, false);
089    
090            expect(component.getSpecifiedId()).andReturn(id);
091    
092            component.setName(name);
093            component.setClientId(name);
094    
095            return component;
096        }
097    
098        private IFormComponent newFormComponent(String id, String extendedId, Location location)
099        {
100            IFormComponent component = newMock(IFormComponent.class);
101    
102            expect(component.getSpecifiedId()).andReturn(id);
103    
104            trainGetExtendedId(component, extendedId);
105            trainGetLocation(component, location);
106    
107            return component;
108        }
109    
110        protected void trainCycleSeedEncoding(IRequestCycle cycle)
111        {
112            expect(cycle.encodeIdState()).andReturn("ENCODED").anyTimes();
113            
114            expect(cycle.getUniqueId(isA(String.class))).andAnswer(new UniqueIdAnswer()).anyTimes();
115        }
116    
117        public class UniqueIdAnswer implements IAnswer<String> {
118    
119            IdAllocator _allocator = new IdAllocator();
120    
121            public String answer()
122                    throws Throwable
123            {
124                return _allocator.allocateId((String) EasyMock.getCurrentArguments()[0]);
125            }
126        }
127    
128        public void test_Cancel_Rewind()
129        {
130            IMarkupWriter writer = newWriter();
131            IRequestCycle cycle = newCycle();
132            IValidationDelegate delegate = newDelegate();
133            MockForm form = new MockForm(delegate);
134    
135            trainIsRewound(cycle, form, true);
136    
137            trainGetPageRenderSupport(cycle, null);
138    
139            replay();
140    
141            final FormSupport fs = newFormSupport(writer, cycle, form);
142    
143            verify();
144    
145            delegate.clear();
146    
147            trainGetParameter(cycle, FormSupportImpl.SUBMIT_MODE, "cancel");
148    
149            // Create a body, just to provie it doesn't get invoked.
150    
151            IRender body = newMock(IRender.class);
152    
153            form.setBody(body);
154    
155            replay();
156    
157            assertEquals(FormConstants.SUBMIT_CANCEL, fs.rewind());
158    
159            verify();
160        }
161    
162        public void test_Complex_Render()
163        {
164            IMarkupWriter writer = newWriter();
165            NestedMarkupWriter nested = newNestedWriter();
166            IRequestCycle cycle = newCycle();
167            ResponseBuilder builder = newMock(ResponseBuilder.class);
168            IValidationDelegate delegate = newDelegate();
169            ILink link = newLink();
170            IRender render = newRender();
171            MockForm form = new MockForm(delegate);
172    
173            trainIsRewound(cycle, form, false);
174    
175            PageRenderSupport support = newPageRenderSupport();
176    
177            trainGetPageRenderSupport(cycle, support);
178    
179            replay();
180    
181            final FormSupport fs = newFormSupport(writer, cycle, form);
182    
183            verify();
184    
185            trainCycleSeedEncoding(cycle);
186            
187            final IFormComponent barney1 = newFormComponent("barney", "barney");
188            final IFormComponent wilma = newFormComponent("wilma", "wilma");
189            final IFormComponent barney2 = newFormComponent("barney", "barney_0");
190    
191            IRender body = newComponentsRenderBody(fs, new IFormComponent[] {barney1, wilma, barney2}, nested);
192    
193            form.setBody(body);
194    
195            trainRegister(support, form, "myform");
196    
197            trainGetParameterNames(link, new String[] {"service"});
198            trainGetParameterValues(link, "service", new String[] {"fred"});
199    
200            trainGetNestedWriter(writer, nested);
201    
202            trainGetURL(link, null, "/app");
203    
204            writer.begin("form");
205            writer.attribute("method", "post");
206            writer.attribute("action", "/app");
207    
208            writer.attribute("id", "myform");
209    
210            render.render(writer, cycle);
211    
212            writer.println();
213    
214            trainHiddenBlock(cycle, builder, writer, form, "fred", "barney,wilma,barney_0");
215    
216            nested.close();
217    
218            writer.end();
219    
220            trainGetFocusField(delegate, "wilma");
221    
222            expect(cycle.isFocusDisabled()).andReturn(false);
223    
224            // effectively means someone else has already claimed focus
225    
226            trainGetFieldFocus(cycle, null);
227    
228            support.addInitializationScript(form, "dojo.require(\"tapestry.form\");");
229            support.addScriptAfterInitialization(form, "tapestry.form.focusField('wilma');");
230            cycle.setAttribute(FormSupportImpl.FIELD_FOCUS_ATTRIBUTE, Boolean.TRUE);
231    
232            replay();
233    
234            fs.render("post", render, link, null, null);
235    
236            verify();
237        }
238    
239        public void test_Complex_Rewind()
240        {
241            IMarkupWriter writer = newWriter();
242            IRequestCycle cycle = newCycle();
243            IValidationDelegate delegate = newDelegate();
244            MockForm form = new MockForm(delegate);
245            ListenerInvoker listenerInvoker = newMock(ListenerInvoker.class);
246    
247            ComponentEventInvoker invoker = new ComponentEventInvoker();
248            invoker.setInvoker(listenerInvoker);
249    
250            trainIsRewound(cycle, form, true);
251            trainGetPageRenderSupport(cycle, null);
252    
253            replay();
254    
255            final FormSupport fs = newFormSupport(writer, cycle, form);
256    
257            verify();
258            
259            delegate.clear();
260    
261            trainCycleForRewind(cycle, "barney,wilma,barney_0", null);
262            trainCycleSeedEncoding(cycle);
263    
264            final IFormComponent barney1 = newFormComponent("barney", "barney");
265            final IFormComponent wilma = newFormComponent("wilma", "wilma");
266            final IFormComponent barney2 = newFormComponent("barney", "barney_0");
267    
268            IRender body = newComponentsRenderBody(fs, new IFormComponent[] {barney1, wilma, barney2}, writer);
269    
270            form.setBody(body);
271            form.setEventInvoker(invoker);
272    
273            trainExtractBrowserEvent(cycle);
274    
275            replay();
276    
277            Map props = new HashMap();
278            props.put("id", "bsId");
279            BrowserEvent event = new BrowserEvent("onclick", new EventTarget(props));
280    
281            invoker.invokeFormListeners(fs, cycle, event);
282    
283            assertEquals(FormConstants.SUBMIT_NORMAL, fs.rewind());
284    
285            verify();
286        }
287    
288        public void test_Complex_Submit_Event_Handler()
289        {
290            IMarkupWriter writer = newWriter();
291            NestedMarkupWriter nested = newNestedWriter();
292            IRequestCycle cycle = newCycle();
293            ResponseBuilder builder = newMock(ResponseBuilder.class);
294            IValidationDelegate delegate = newDelegate();
295            PageRenderSupport support = newPageRenderSupport();
296            ILink link = newLink();
297            IRender render = newRender();
298    
299            MockForm form = new MockForm(delegate);
300    
301            trainIsRewound(cycle, form, false);
302            trainGetPageRenderSupport(cycle, support);
303    
304            replay();
305    
306            final FormSupport fs = newFormSupport(writer, cycle, form);
307    
308            verify();
309    
310            trainCycleSeedEncoding(cycle);
311    
312            form.setBody(new IRender() {
313                public void render(IMarkupWriter pwriter, IRequestCycle pcycle)
314                {
315                    fs.addEventHandler(FormEventType.SUBMIT, "mySubmit1");
316                    fs.addEventHandler(FormEventType.SUBMIT, "mySubmit2");
317                    fs.addEventHandler(FormEventType.SUBMIT, "mySubmit3");
318                }
319            });
320    
321            trainRegister(support, form, "myform");
322            trainGetParameterNames(link, new String[] {"service"});
323            trainGetParameterValues(link, "service", new String[] {"fred"});
324    
325            trainGetNestedWriter(writer, nested);
326    
327            trainGetURL(link, null, "/app");
328    
329            writer.begin("form");
330            writer.attribute("method", "post");
331            writer.attribute("action", "/app");
332    
333            writer.attribute("id", "myform");
334    
335            support.addInitializationScript(form, "Tapestry.onsubmit('myform', function (event)"
336                                                  + "\n{\n  mySubmit1();\n  mySubmit2();\n  mySubmit3();\n});\n");
337    
338            render.render(writer, cycle);
339            writer.println();
340    
341            trainHiddenBlock(cycle, builder, writer, form, "fred", "");
342    
343            nested.close();
344            writer.end();
345    
346            support.addInitializationScript(form, "dojo.require(\"tapestry.form\");");
347    
348            // Side test: what if no focus field?
349    
350            trainGetFocusField(delegate, null);
351    
352            expect(cycle.isFocusDisabled()).andReturn(false);
353    
354            replay();
355    
356            fs.render("post", render, link, null, null);
357    
358            verify();
359        }
360    
361        public void test_Encoding_Type()
362        {
363            IMarkupWriter writer = newWriter();
364            NestedMarkupWriter nested = newNestedWriter();
365            IRequestCycle cycle = newCycle();
366            ResponseBuilder builder = newMock(ResponseBuilder.class);
367            IValidationDelegate delegate = newDelegate();
368            PageRenderSupport support = newPageRenderSupport();
369            ILink link = newLink();
370            IRender render = newRender();
371    
372            MockForm form = new MockForm(delegate);
373    
374            trainIsRewound(cycle, form, false);
375    
376            trainGetPageRenderSupport(cycle, support);
377    
378            replay();
379    
380            final FormSupport fs = newFormSupport(writer, cycle, form);
381    
382            verify();
383    
384            trainCycleSeedEncoding(cycle);
385            
386            form.setBody(new IRender() {
387                public void render(IMarkupWriter pwriter, IRequestCycle pcycle)
388                {
389                    fs.setEncodingType("foo/bar");
390                }
391            });
392    
393            trainRegister(support, form, "myform");
394    
395            trainGetParameterNames(link, new String[] {"service"});
396            trainGetParameterValues(link, "service", new String[] {"fred"});
397            
398            trainGetNestedWriter(writer, nested);
399    
400            trainGetURL(link, null, "/app");
401    
402            writer.begin("form");
403            writer.attribute("method", "post");
404            writer.attribute("action", "/app");
405    
406            writer.attribute("id", "myform");
407            writer.attribute("enctype", "foo/bar");
408    
409            render.render(writer, cycle);
410    
411            writer.println();
412    
413            trainHiddenBlock(cycle, builder, writer, form, "fred", "");
414    
415            nested.close();
416    
417            writer.end();
418    
419            support.addInitializationScript(form, "dojo.require(\"tapestry.form\");");
420    
421            trainGetFocusField(delegate, null);
422    
423            expect(cycle.isFocusDisabled()).andReturn(false);
424    
425            replay();
426    
427            fs.render("post", render, link, null, null);
428    
429            verify();
430        }
431    
432        public void test_Field_Prerender_Twice()
433        {
434            IFormComponent field = newField();
435            IMarkupWriter writer = newWriter();
436            NestedMarkupWriter nested = newNestedWriter();
437            IRequestCycle cycle = newCycle();
438            Location l = newLocation();
439    
440            ResponseBuilder builder = newMock(ResponseBuilder.class);
441    
442            trainGetExtendedId(field, "foo.bar");
443            trainGetNestedWriter(writer, nested);
444    
445            expect(cycle.getAttribute(TapestryUtils.FIELD_PRERENDER)).andReturn(null);
446            cycle.setAttribute(TapestryUtils.FIELD_PRERENDER, field);
447    
448            expect(cycle.getResponseBuilder()).andReturn(builder);
449    
450            builder.render(nested, field, cycle);
451    
452            cycle.removeAttribute(TapestryUtils.FIELD_PRERENDER);
453    
454            expect(nested.getBuffer()).andReturn("NESTED CONTENT");
455    
456            replay();
457    
458            FormSupport fs = new FormSupportImpl(cycle);
459    
460            fs.prerenderField(writer, field, l);
461    
462            verify();
463    
464            trainGetExtendedId(field, "foo.bar");
465    
466            replay();
467    
468            try
469            {
470                fs.prerenderField(writer, field, l);
471                unreachable();
472            }
473            catch (ApplicationRuntimeException ex) {
474                assertEquals(
475                        "Field EasyMock for interface org.apache.tapestry.form.IFormComponent has already been pre-rendered. "
476                        + "This exception may indicate that a FieldLabel rendered, but the corresponding field did not.",
477                        ex.getMessage());
478    
479                assertSame(l, ex.getLocation());
480                assertSame(field, ex.getComponent());
481            }
482    
483            verify();
484    
485        }
486    
487        public void test_Hidden_Values()
488        {
489            IMarkupWriter writer = newWriter();
490            NestedMarkupWriter nested = newNestedWriter();
491            IRequestCycle cycle = newCycle();
492            ResponseBuilder builder = newMock(ResponseBuilder.class);
493            IValidationDelegate delegate = newDelegate();
494            PageRenderSupport support = newPageRenderSupport();
495            ILink link = newLink();
496            IRender render = newRender();
497    
498            MockForm form = new MockForm(delegate);
499    
500            trainIsRewound(cycle, form, false);
501            trainGetPageRenderSupport(cycle, support);
502    
503            replay();
504    
505            final FormSupport fs = newFormSupport(writer, cycle, form);
506    
507            verify();
508    
509            trainCycleSeedEncoding(cycle);
510            
511            form.setBody(new IRender() {
512                public void render(IMarkupWriter pwriter, IRequestCycle pcycle)
513                {
514                    fs.addHiddenValue("hidden1", "value1");
515                    fs.addHiddenValue("hidden2", "id2", "value2");
516                }
517            });
518    
519            trainRegister(support, form, "myform");
520            trainGetParameterNames(link, new String[]{"service"});
521            trainGetParameterValues(link, "service", new String[] {"fred"});
522    
523            trainGetNestedWriter(writer, nested);
524    
525            trainGetURL(link, null, "/app");
526    
527            writer.begin("form");
528            writer.attribute("method", "post");
529            writer.attribute("action", "/app");
530    
531            writer.attribute("id", "myform");
532    
533            render.render(writer, cycle);
534    
535            writer.println();
536    
537            expect(cycle.getResponseBuilder()).andReturn(builder);
538    
539            expect(builder.contains(form)).andReturn(false);
540    
541            trainDiv(writer, form);
542    
543    
544            trainHidden(writer, "formids", "");
545            trainHidden(writer, "seedids", "ENCODED");
546            trainHidden(writer, "service", "fred");
547            trainHidden(writer, "submitmode", "");
548            trainHidden(writer, FormConstants.SUBMIT_NAME_PARAMETER, "");
549            trainHidden(writer, "hidden1", "value1");
550            trainHidden(writer, "hidden2", "id2", "value2");
551    
552            writer.end();
553    
554            nested.close();
555    
556            writer.end();
557    
558            support.addInitializationScript(form, "dojo.require(\"tapestry.form\");");
559    
560            trainGetFocusField(delegate, null);
561    
562            expect(cycle.isFocusDisabled()).andReturn(false);
563    
564            replay();
565    
566            fs.render("post", render, link, null, null);
567    
568            verify();
569        }
570    
571        public void test_Invalid_Encoding_Type()
572        {
573            IMarkupWriter writer = newWriter();
574            NestedMarkupWriter nested = newNestedWriter();
575            IRequestCycle cycle = newCycle();
576            IValidationDelegate delegate = newDelegate();
577            PageRenderSupport support = newPageRenderSupport();
578            ILink link = newLink();
579            IRender render = newRender();
580    
581            MockForm form = new MockForm(delegate);
582    
583            trainIsRewound(cycle, form, false);
584    
585            trainGetPageRenderSupport(cycle, support);
586    
587            replay();
588    
589            final FormSupport fs = newFormSupport(writer, cycle, form);
590    
591            verify();
592    
593            trainCycleSeedEncoding(cycle);
594            
595            form.setBody(new IRender() {
596                public void render(IMarkupWriter pwriter, IRequestCycle pcycle)
597                {
598                    fs.setEncodingType("foo/bar");
599                    fs.setEncodingType("zip/zap");
600                }
601            });
602    
603            trainRegister(support, form, "myform");
604    
605            trainGetParameterNames(link, new String[]{"service"});
606            trainGetParameterValues(link, "service", new String[]{"fred"});
607    
608            trainGetNestedWriter(writer, nested);
609    
610            replay();
611    
612            try {
613                fs.render("post", render, link, null, null);
614                unreachable();
615            }
616            catch (ApplicationRuntimeException ex) {
617                assertEquals(
618                        "Components within form SomePage/myform have requested conflicting encoding types 'foo/bar' and 'zip/zap'.",
619                        ex.getMessage());
620                assertSame(form, ex.getComponent());
621            }
622    
623            verify();
624        }
625    
626        public void test_Refresh_Rewind()
627        {
628            IMarkupWriter writer = newWriter();
629            IRequestCycle cycle = newCycle();
630            IValidationDelegate delegate = newDelegate();
631            MockForm form = new MockForm(delegate);
632            ComponentEventInvoker invoker = org.easymock.classextension.EasyMock.createMock(ComponentEventInvoker.class);
633    
634            trainIsRewound(cycle, form, true);
635            trainGetPageRenderSupport(cycle, null);
636    
637            replay();
638    
639            final FormSupport fs = newFormSupport(writer, cycle, form);
640    
641            verify();
642    
643            trainCycleSeedEncoding(cycle);
644            delegate.clear();
645    
646            trainCycleForRewind(cycle, "refresh", "barney", null);
647    
648            final IFormComponent component = newFormComponent("barney", "barney");
649    
650            IRender body = newComponentRenderBody(fs, component, writer);
651    
652            form.setBody(body);
653            form.setEventInvoker(invoker);
654    
655            trainExtractBrowserEvent(cycle);
656    
657            invoker.invokeFormListeners(eq(fs), eq(cycle), isA(BrowserEvent.class));
658    
659            delegate.clearErrors();
660    
661            replay();
662    
663            assertEquals(FormConstants.SUBMIT_REFRESH, fs.rewind());
664    
665            verify();
666        }
667    
668        public void test_Render_Extra_Reserved_Ids()
669        {
670            IMarkupWriter writer = newWriter();
671            NestedMarkupWriter nested = newNestedWriter();
672            IRequestCycle cycle = newCycle();
673            ResponseBuilder builder = newMock(ResponseBuilder.class);
674            IValidationDelegate delegate = newDelegate();
675            PageRenderSupport support = newPageRenderSupport();
676            ILink link = newLink();
677            IRender render = newRender();
678    
679            MockForm form = new MockForm(delegate);
680    
681            trainIsRewound(cycle, form, false);
682    
683            trainGetPageRenderSupport(cycle, support);
684    
685            replay();
686    
687            final FormSupport fs = newFormSupport(writer, cycle, form);
688    
689            verify();
690    
691            trainCycleSeedEncoding(cycle);
692            
693            final IFormComponent component = newFormComponent("action", "action_0");
694    
695            IRender body = newComponentRenderBody(fs, component, nested);
696    
697            form.setBody(body);
698    
699            trainRegister(support, form, "myform");
700            trainGetParameterNames(link, new String[]{"action"});
701            trainGetParameterValues(link, "action", new String[] {"fred"});
702    
703            trainGetNestedWriter(writer, nested);
704    
705            trainGetURL(link, null, "/app");
706    
707            writer.begin("form");
708            writer.attribute("method", "post");
709            writer.attribute("action", "/app");
710    
711            writer.attribute("id", "myform");
712    
713            render.render(writer, cycle);
714    
715            writer.println();
716    
717            expect(cycle.getResponseBuilder()).andReturn(builder);
718            expect(builder.contains(form)).andReturn(false);
719    
720            trainDiv(writer, form);
721    
722            trainHidden(writer, "formids", "action_0");
723            trainHidden(writer, "seedids", "ENCODED");
724            trainHidden(writer, "action", "fred");
725            trainHidden(writer, "reservedids", "action");
726            trainHidden(writer, "submitmode", "");
727            trainHidden(writer, FormConstants.SUBMIT_NAME_PARAMETER, "");
728    
729            writer.end();
730            nested.close();
731            writer.end();
732    
733            support.addInitializationScript(form, "dojo.require(\"tapestry.form\");");
734    
735            trainGetFocusField(delegate, null);
736    
737            expect(cycle.isFocusDisabled()).andReturn(false);
738    
739            replay();
740    
741            fs.render("post", render, link, null, null);
742    
743            verify();
744        }
745    
746        public void test_Reset_Event_Handler()
747        {
748            IMarkupWriter writer = newWriter();
749            NestedMarkupWriter nested = newNestedWriter();
750            IRequestCycle cycle = newCycle();
751            ResponseBuilder builder = newMock(ResponseBuilder.class);
752    
753            IValidationDelegate delegate = newDelegate();
754            PageRenderSupport support = newPageRenderSupport();
755            ILink link = newLink();
756            IRender render = newRender();
757    
758            MockForm form = new MockForm(delegate);
759    
760            trainIsRewound(cycle, form, false);
761    
762            trainGetPageRenderSupport(cycle, support);
763    
764            replay();
765    
766            final FormSupport fs = newFormSupport(writer, cycle, form);
767    
768            verify();
769    
770            trainCycleSeedEncoding(cycle);
771            form.setBody(new IRender() {
772                public void render(IMarkupWriter pwriter, IRequestCycle pcycle)
773                {
774                    fs.addEventHandler(FormEventType.RESET, "myReset1");
775                    fs.addEventHandler(FormEventType.RESET, "myReset2");
776                }
777            });
778    
779            trainRegister(support, form, "myform");
780    
781            trainGetParameterNames(link, new String[] {"service"});
782            trainGetParameterValues(link, "service", new String[] {"fred"});
783    
784            trainGetNestedWriter(writer, nested);
785    
786            trainGetURL(link, null, "/app");
787    
788            writer.begin("form");
789            writer.attribute("method", "post");
790            writer.attribute("action", "/app");
791    
792            writer.attribute("id", "myform");
793    
794            support.addInitializationScript(form, "Tapestry.onreset('myform', function (event)"
795                                                  + "\n{\n  myReset1();\n  myReset2();\n});\n");
796    
797            render.render(writer, cycle);
798    
799            writer.println();
800    
801            trainHiddenBlock(cycle, builder, writer, form, "fred", "");
802    
803            nested.close();
804            writer.end();
805    
806            support.addInitializationScript(form, "dojo.require(\"tapestry.form\");");
807    
808            trainGetFocusField(delegate, null);
809    
810            expect(cycle.isFocusDisabled()).andReturn(false);
811    
812            replay();
813    
814            fs.render("post", render, link, null, null);
815    
816            verify();
817        }
818    
819        public void test_Rewind_Extra_Reserved_Ids()
820        {
821            IMarkupWriter writer = newWriter();
822            IRequestCycle cycle = newCycle();
823            IValidationDelegate delegate = newDelegate();
824            ComponentEventInvoker invoker =
825                    org.easymock.classextension.EasyMock.createMock(ComponentEventInvoker.class);
826    
827            MockForm form = new MockForm(delegate);
828    
829            trainIsRewound(cycle, form, true);
830    
831            trainGetPageRenderSupport(cycle, null);
832    
833            replay();
834    
835            final FormSupport fs = newFormSupport(writer, cycle, form);
836    
837            verify();
838    
839            trainCycleSeedEncoding(cycle);
840            delegate.clear();
841    
842            trainCycleForRewind(cycle, "action_0", "action");
843    
844            final IFormComponent component = newFormComponent("action", "action_0");
845    
846            IRender body = newComponentRenderBody(fs, component, writer);
847    
848            form.setBody(body);
849            form.setEventInvoker(invoker);
850    
851            trainExtractBrowserEvent(cycle);
852    
853            invoker.invokeFormListeners(eq(fs), eq(cycle), isA(BrowserEvent.class));
854    
855            replay();
856    
857            assertEquals(FormConstants.SUBMIT_NORMAL, fs.rewind());
858    
859            verify();
860        }
861    
862        public void test_Rewind_Mismatch()
863        {
864            IMarkupWriter writer = newWriter();
865            IRequestCycle cycle = newCycle();
866            IValidationDelegate delegate = newDelegate();
867    
868            MockForm form = new MockForm(delegate);
869    
870            trainIsRewound(cycle, form, true);
871            trainGetPageRenderSupport(cycle, null);
872    
873            replay();
874    
875            final FormSupport fs = newFormSupport(writer, cycle, form);
876    
877            verify();
878    
879            trainCycleSeedEncoding(cycle);
880            Location l = newLocation();
881    
882            delegate.clear();
883    
884            // So, the scenario here is that component "pebbles" was inside
885            // some kind of conditional that evaluated to true during the render,
886            // but is now false on the rewind.
887    
888            trainCycleForRewind(cycle, "barney,wilma,pebbles,barney_0", null);
889    
890            final IFormComponent barney1 = newFormComponent("barney", "barney");
891            final IFormComponent wilma = newFormComponent("wilma", "wilma");
892            final IFormComponent barney2 = newFormComponent("barney", "SomePage/barney", l);
893    
894            IRender body = newComponentsRenderBody(fs, new IFormComponent[]
895                    {barney1, wilma, barney2}, writer);
896    
897            form.setBody(body);
898    
899            replay();
900    
901            try {
902                fs.rewind();
903                unreachable();
904            }
905            catch (StaleLinkException ex) {
906                assertEquals(
907                        "Rewind of form SomePage/myform expected allocated id #3 to be 'pebbles', but was 'barney_0' (requested by component SomePage/barney).",
908                        ex.getMessage());
909                assertSame(barney2, ex.getComponent());
910                assertSame(l, ex.getLocation());
911            }
912    
913            verify();
914        }
915    
916        public void test_Rewind_Too_Long()
917        {
918            IMarkupWriter writer = newWriter();
919            IRequestCycle cycle = newCycle();
920            IValidationDelegate delegate = newDelegate();
921    
922            MockForm form = new MockForm(delegate);
923    
924            trainIsRewound(cycle, form, true);
925            trainGetPageRenderSupport(cycle, null);
926    
927            replay();
928    
929            final FormSupport fs = newFormSupport(writer, cycle, form);
930    
931            verify();
932    
933            trainCycleSeedEncoding(cycle);
934            
935            Location l = newLocation();
936            delegate.clear();
937    
938            // So, the scenario here is that component "barney" was inside
939            // some kind of loop that executed once on the render, but twice
940            // on the rewind (i.e., an additional object was added in between).
941    
942            trainCycleForRewind(cycle, "barney,wilma", null);
943    
944            final IFormComponent barney1 = newFormComponent("barney", "barney");
945            final IFormComponent wilma = newFormComponent("wilma", "wilma");
946            final IFormComponent barney2 = newFormComponent("barney", "SomePage/barney", l);
947    
948            IRender body = newComponentsRenderBody(fs, new IFormComponent[]
949                    {barney1, wilma, barney2}, writer);
950    
951            form.setBody(body);
952    
953            replay();
954    
955            try {
956                fs.rewind();
957                unreachable();
958            }
959            catch (StaleLinkException ex) {
960                assertEquals(
961                        "Rewind of form SomePage/myform expected only 2 form elements, but an additional id was requested by component SomePage/barney.",
962                        ex.getMessage());
963                assertSame(barney2, ex.getComponent());
964                assertSame(l, ex.getLocation());
965            }
966    
967            verify();
968        }
969    
970        public void test_Rewind_Too_Short()
971        {
972            Location l = newLocation();
973            IMarkupWriter writer = newWriter();
974            IRequestCycle cycle = newCycle();
975            IValidationDelegate delegate = newDelegate();
976            ComponentEventInvoker invoker = org.easymock.classextension.EasyMock.createMock(ComponentEventInvoker.class);
977    
978            MockForm form = new MockForm(delegate, l);
979    
980            trainIsRewound(cycle, form, true);
981            trainGetPageRenderSupport(cycle, null);
982    
983            replay();
984    
985            final FormSupport fs = newFormSupport(writer, cycle, form);
986    
987            verify();
988    
989            trainCycleSeedEncoding(cycle);
990            delegate.clear();
991    
992            // So, the scenario here is that component "barney" was inside
993            // some kind of loop that executed twice on the render, but only once
994            // on the rewind (i.e., the object was deleted in between).
995    
996            trainCycleForRewind(cycle, "barney,wilma,barney$0", null);
997    
998            final IFormComponent barney1 = newFormComponent("barney", "barney");
999            final IFormComponent wilma = newFormComponent("wilma", "wilma");
1000    
1001            IRender body = newComponentsRenderBody(fs, new IFormComponent[]{barney1, wilma}, writer);
1002    
1003            form.setBody(body);
1004            form.setEventInvoker(invoker);
1005    
1006            trainExtractBrowserEvent(cycle);
1007    
1008            invoker.invokeFormListeners(eq(fs), eq(cycle), isA(BrowserEvent.class));
1009    
1010            replay();
1011    
1012            try {
1013                fs.rewind();
1014                unreachable();
1015            }
1016            catch (StaleLinkException ex) {
1017                assertEquals(
1018                        "Rewind of form SomePage/myform expected 1 more form elements, starting with id 'barney$0'.",
1019                        ex.getMessage());
1020                assertSame(form, ex.getComponent());
1021                assertSame(l, ex.getLocation());
1022            }
1023    
1024            verify();
1025        }
1026    
1027        public void test_Simple_Render()
1028        {
1029            IMarkupWriter writer = newWriter();
1030            NestedMarkupWriter nested = newNestedWriter();
1031            IRequestCycle cycle = newCycle();
1032            ResponseBuilder builder = newMock(ResponseBuilder.class);
1033            IValidationDelegate delegate = newDelegate();
1034            ILink link = newLink();
1035            IRender render = newRender();
1036    
1037            MockForm form = new MockForm(delegate);
1038    
1039            trainIsRewound(cycle, form, false);
1040    
1041            PageRenderSupport support = newPageRenderSupport();
1042    
1043            trainGetPageRenderSupport(cycle, support);
1044    
1045            replay();
1046    
1047            final FormSupport fs = newFormSupport(writer, cycle, form);
1048    
1049            verify();
1050    
1051            trainCycleSeedEncoding(cycle);
1052            final IFormComponent component = newFormComponent("barney", "barney");
1053    
1054            IRender body = newComponentRenderBody(fs, component, nested);
1055    
1056            form.setBody(body);
1057    
1058            trainRegister(support, form, "myform");
1059    
1060            trainGetParameterNames(link, new String[]
1061                    {"service"});
1062            trainGetParameterValues(link, "service", new String[]
1063                    {"fred"});
1064    
1065            trainGetNestedWriter(writer, nested);
1066    
1067            trainGetURL(link, null, "/app");
1068    
1069            writer.begin("form");
1070            writer.attribute("method", "post");
1071            writer.attribute("action", "/app");
1072    
1073            writer.attribute("id", "myform");
1074    
1075            render.render(writer, cycle);
1076    
1077            writer.println();
1078    
1079            trainHiddenBlock(cycle, builder, writer, form, "fred", "barney");
1080    
1081            nested.close();
1082    
1083            writer.end();
1084    
1085            trainGetFocusField(delegate, "barney");
1086    
1087            expect(cycle.isFocusDisabled()).andReturn(false);
1088    
1089            // Side test: check for another form already grabbing focus
1090    
1091            trainGetFieldFocus(cycle, null);
1092    
1093            support.addInitializationScript(form, "dojo.require(\"tapestry.form\");");
1094            support.addScriptAfterInitialization(form, "tapestry.form.focusField('barney');");
1095            cycle.setAttribute(FormSupportImpl.FIELD_FOCUS_ATTRIBUTE, Boolean.TRUE);
1096    
1097            replay();
1098    
1099            fs.render("post", render, link, null, null);
1100    
1101            verify();
1102        }
1103    
1104        public void test_Simple_Render_With_Deferred_Runnable()
1105        {
1106            IMarkupWriter writer = newWriter();
1107            NestedMarkupWriter nested = newNestedWriter();
1108            IRequestCycle cycle = newCycle();
1109            ResponseBuilder builder = newMock(ResponseBuilder.class);
1110            IValidationDelegate delegate = newDelegate();
1111            ILink link = newLink();
1112            IRender render = newRender();
1113    
1114            MockForm form = new MockForm(delegate);
1115    
1116            trainIsRewound(cycle, form, false);
1117    
1118            PageRenderSupport support = newPageRenderSupport();
1119    
1120            trainGetPageRenderSupport(cycle, support);
1121    
1122            replay();
1123    
1124            final FormSupport fs = newFormSupport(writer, cycle, form);
1125    
1126            verify();
1127    
1128            trainCycleSeedEncoding(cycle);
1129            IRender body = new IRender() {
1130                public void render(final IMarkupWriter pwriter, IRequestCycle pcycle)
1131                {
1132                    fs.addDeferredRunnable(new Runnable() {
1133    
1134                        public void run()
1135                        {
1136                            pwriter.print("DEFERRED");
1137                        }
1138    
1139                    });
1140                }
1141    
1142            };
1143    
1144            form.setBody(body);
1145    
1146            trainRegister(support, form, "myform");
1147    
1148            trainGetParameterNames(link, new String[] {"service"});
1149            trainGetParameterValues(link, "service", new String[]{"fred"});
1150            
1151            trainGetNestedWriter(writer, nested);
1152    
1153            nested.print("DEFERRED");
1154    
1155            trainGetURL(link, null, "/app");
1156    
1157            writer.begin("form");
1158            writer.attribute("method", "post");
1159            writer.attribute("action", "/app");
1160    
1161            writer.attribute("id", "myform");
1162    
1163            render.render(writer, cycle);
1164    
1165            writer.println();
1166    
1167            trainHiddenBlock(cycle, builder, writer, form, "fred", "");
1168    
1169            nested.close();
1170    
1171            writer.end();
1172    
1173            support.addInitializationScript(form, "dojo.require(\"tapestry.form\");");
1174    
1175            trainGetFocusField(delegate, null);
1176    
1177            expect(cycle.isFocusDisabled()).andReturn(false);
1178    
1179            replay();
1180    
1181            fs.render("post", render, link, null, null);
1182    
1183            verify();
1184        }
1185    
1186        public void test_Simple_Render_With_Scheme()
1187        {
1188            IMarkupWriter writer = newWriter();
1189            NestedMarkupWriter nested = newNestedWriter();
1190            IRequestCycle cycle = newCycle();
1191            ResponseBuilder builder = newMock(ResponseBuilder.class);
1192            IValidationDelegate delegate = newDelegate();
1193            ILink link = newLink();
1194            IRender render = newRender();
1195    
1196            MockForm form = new MockForm(delegate);
1197    
1198            trainIsRewound(cycle, form, false);
1199    
1200            PageRenderSupport support = newPageRenderSupport();
1201    
1202            trainGetPageRenderSupport(cycle, support);
1203    
1204            replay();
1205    
1206            final FormSupport fs = newFormSupport(writer, cycle, form);
1207    
1208            verify();
1209    
1210            trainCycleSeedEncoding(cycle);
1211            final IFormComponent component = newFormComponent("barney", "barney");
1212    
1213            IRender body = newComponentRenderBody(fs, component, nested);
1214    
1215            form.setBody(body);
1216    
1217            trainRegister(support, form, "myform");
1218    
1219            trainGetParameterNames(link, new String[]
1220                    {"service"});
1221            trainGetParameterValues(link, "service", new String[]
1222                    {"fred"});
1223    
1224            trainGetNestedWriter(writer, nested);
1225    
1226            trainGetURL(link, "https", "https://foo.bar/app", 443);
1227    
1228            writer.begin("form");
1229            writer.attribute("method", "post");
1230            writer.attribute("action", "https://foo.bar/app");
1231    
1232            writer.attribute("id", "myform");
1233    
1234            render.render(writer, cycle);
1235    
1236            writer.println();
1237    
1238            trainHiddenBlock(cycle, builder, writer, form, "fred", "barney");
1239    
1240            nested.close();
1241    
1242            writer.end();
1243    
1244            support.addInitializationScript(form, "dojo.require(\"tapestry.form\");");
1245    
1246            trainGetFocusField(delegate, "barney");
1247    
1248            expect(cycle.isFocusDisabled()).andReturn(false);
1249    
1250            // Side test: check for another form already grabbing focus
1251    
1252            trainGetFieldFocus(cycle, Boolean.TRUE);
1253    
1254            // support.addInitializationScript(form, "tapestry.form.focusField('barney');");
1255    
1256            // cycle.setAttribute(FormSupportImpl.FIELD_FOCUS_ATTRIBUTE, true);
1257    
1258            replay();
1259    
1260            fs.render("post", render, link, "https", new Integer(443));
1261    
1262            verify();
1263        }
1264    
1265        public void test_Simple_Rewind()
1266        {
1267            IMarkupWriter writer = newWriter();
1268            IRequestCycle cycle = newCycle();
1269            IValidationDelegate delegate = newDelegate();
1270            MockForm form = new MockForm(delegate);
1271            ComponentEventInvoker invoker =
1272                    org.easymock.classextension.EasyMock.createMock(ComponentEventInvoker.class);
1273    
1274            trainIsRewound(cycle, form, true);
1275    
1276            trainGetPageRenderSupport(cycle, null);
1277    
1278            replay();
1279    
1280            final FormSupport fs = newFormSupport(writer, cycle, form);
1281    
1282            verify();
1283    
1284            trainCycleSeedEncoding(cycle);
1285            delegate.clear();
1286    
1287            trainCycleForRewind(cycle, "barney", null);
1288    
1289            final IFormComponent component = newFormComponent("barney", "barney");
1290    
1291            IRender body = newComponentRenderBody(fs, component, writer);
1292    
1293            form.setBody(body);
1294            form.setEventInvoker(invoker);
1295    
1296            trainExtractBrowserEvent(cycle);
1297    
1298            invoker.invokeFormListeners(eq(fs), eq(cycle), isA(BrowserEvent.class));
1299    
1300            replay();
1301    
1302            assertEquals(FormConstants.SUBMIT_NORMAL, fs.rewind());
1303    
1304            verify();
1305        }
1306    
1307        public void test_Simple_Rewind_With_Deferred_Runnable()
1308        {
1309            IMarkupWriter writer = newWriter();
1310            IRequestCycle cycle = newCycle();
1311            IValidationDelegate delegate = newDelegate();
1312            MockForm form = new MockForm(delegate);
1313            ComponentEventInvoker invoker =
1314                    org.easymock.classextension.EasyMock.createMock(ComponentEventInvoker.class);
1315    
1316            trainIsRewound(cycle, form, true);
1317    
1318            trainGetPageRenderSupport(cycle, null);
1319    
1320            replay();
1321    
1322            final FormSupport fs = newFormSupport(writer, cycle, form);
1323    
1324            verify();
1325    
1326            trainCycleSeedEncoding(cycle);
1327            delegate.clear();
1328    
1329            trainCycleForRewind(cycle, "", null);
1330            trainExtractBrowserEvent(cycle);
1331    
1332            writer.print("DEFERRED");
1333    
1334            invoker.invokeFormListeners(eq(fs), eq(cycle), isA(BrowserEvent.class));
1335    
1336            replay();
1337    
1338            IRender body = new IRender() {
1339    
1340                public void render(final IMarkupWriter pwriter, IRequestCycle pcycle)
1341                {
1342                    fs.addDeferredRunnable(new Runnable() {
1343                        public void run()
1344                        {
1345                            pwriter.print("DEFERRED");
1346                        }
1347    
1348                    });
1349                }
1350    
1351            };
1352    
1353            form.setBody(body);
1354            form.setEventInvoker(invoker);
1355    
1356            assertEquals(FormConstants.SUBMIT_NORMAL, fs.rewind());
1357    
1358            verify();
1359        }
1360    
1361        public void test_Simple_Submit_Event_Handler()
1362        {
1363            IMarkupWriter writer = newWriter();
1364            NestedMarkupWriter nested = newNestedWriter();
1365            IRequestCycle cycle = newCycle();
1366            ResponseBuilder builder = newMock(ResponseBuilder.class);
1367            IValidationDelegate delegate = newDelegate();
1368            ILink link = newLink();
1369            IRender render = newRender();
1370    
1371            MockForm form = new MockForm(delegate);
1372    
1373            trainIsRewound(cycle, form, false);
1374    
1375            PageRenderSupport support = newPageRenderSupport();
1376    
1377            trainGetPageRenderSupport(cycle, support);
1378    
1379            replay();
1380    
1381            final FormSupport fs = newFormSupport(writer, cycle, form);
1382    
1383            verify();
1384    
1385            trainCycleSeedEncoding(cycle);
1386            trainRegister(support, form, "myform");
1387    
1388            trainGetParameterNames(link, new String[]
1389                    {"service"});
1390            trainGetParameterValues(link, "service", new String[]
1391                    {"fred"});
1392    
1393            trainGetNestedWriter(writer, nested);
1394    
1395            trainGetURL(link, null, "/app");
1396    
1397            writer.begin("form");
1398            writer.attribute("method", "post");
1399            writer.attribute("action", "/app");
1400    
1401            writer.attribute("id", "myform");
1402    
1403            form.setBody(new IRender() {
1404                public void render(IMarkupWriter pwriter, IRequestCycle pcycle)
1405                {
1406                    fs.addEventHandler(FormEventType.SUBMIT, "mySubmit()");
1407                }
1408            });
1409    
1410            support.addInitializationScript(form, "Tapestry.onsubmit('myform', function (event)"
1411                                                  + "\n{\n  mySubmit();\n});\n");
1412    
1413            render.render(writer, cycle);
1414    
1415            writer.println();
1416    
1417            trainHiddenBlock(cycle, builder, writer, form, "fred", "");
1418    
1419            nested.close();
1420    
1421            writer.end();
1422    
1423            support.addInitializationScript(form, "dojo.require(\"tapestry.form\");");
1424    
1425            trainGetFocusField(delegate, null);
1426    
1427            expect(cycle.isFocusDisabled()).andReturn(false);
1428    
1429            replay();
1430    
1431            fs.render("post", render, link, null, null);
1432    
1433            verify();
1434        }
1435    
1436        private void trainCycleForRewind(IRequestCycle cycle, String allocatedIds, String reservedIds)
1437        {
1438            trainCycleForRewind(cycle, "submit", allocatedIds, reservedIds);
1439        }
1440    
1441        private void trainCycleForRewind(IRequestCycle cycle, String submitMode, String allocatedIds,
1442                                         String reservedIds)
1443        {
1444            trainGetParameter(cycle, FormSupportImpl.SEED_IDS, "");
1445            cycle.initializeIdState("");
1446    
1447            trainGetParameter(cycle, FormSupportImpl.SUBMIT_MODE, submitMode);
1448            trainGetParameter(cycle, FormSupportImpl.FORM_IDS, allocatedIds);
1449            trainGetParameter(cycle, FormSupportImpl.RESERVED_FORM_IDS, reservedIds);
1450        }
1451    
1452        protected void trainDiv(IMarkupWriter writer, IForm form)
1453        {
1454            writer.begin("div");
1455            writer.attribute("style", "display:none;");
1456            writer.attribute("id", form.getName() + "hidden");
1457        }
1458    
1459        private void trainGetFieldFocus(IRequestCycle cycle, Object value)
1460        {
1461            expect(cycle.getAttribute(FormSupportImpl.FIELD_FOCUS_ATTRIBUTE)).andReturn(value);
1462        }
1463    
1464        private void trainGetFocusField(IValidationDelegate delegate, String fieldName)
1465        {
1466            expect(delegate.getFocusField()).andReturn(fieldName);
1467        }
1468    
1469        private void trainGetURL(ILink link, String scheme, String URL, int port)
1470        {
1471            // This will change shortly, with the new scheme parameter passed into FormSupport.render()
1472    
1473            expect(link.getURL(scheme, null, port, null, false)).andReturn(URL);
1474        }
1475    
1476        private void trainGetURL(ILink link, String scheme, String URL)
1477        {
1478            trainGetURL(link, scheme, URL, 0);
1479        }
1480    
1481        private void trainHidden(IMarkupWriter writer, String name, String value)
1482        {
1483            writer.beginEmpty("input");
1484            writer.attribute("type", "hidden");
1485            writer.attribute("name", name);
1486            writer.attribute("value", value);
1487            writer.println();
1488        }
1489    
1490        private void trainHidden(IMarkupWriter writer, String name, String id, String value)
1491        {
1492            writer.beginEmpty("input");
1493            writer.attribute("type", "hidden");
1494            writer.attribute("name", name);
1495            writer.attribute("id", id);
1496            writer.attribute("value", value);
1497            writer.println();
1498        }
1499    
1500        protected void trainHiddenBlock(IRequestCycle cycle, ResponseBuilder builder,
1501                                        IMarkupWriter writer, IForm form,
1502                                        String serviceName, String formIds)
1503        {
1504            expect(cycle.getResponseBuilder()).andReturn(builder);
1505    
1506            expect(builder.contains(form)).andReturn(false);
1507    
1508            trainDiv(writer, form);
1509    
1510            trainHidden(writer, "formids", formIds);
1511            trainHidden(writer, "seedids", "ENCODED");
1512            trainHidden(writer, "service", serviceName);
1513            trainHidden(writer, "submitmode", "");
1514            trainHidden(writer, FormConstants.SUBMIT_NAME_PARAMETER, "");
1515    
1516            writer.end();
1517        }
1518    
1519        protected void trainIsRewound(IRequestCycle cycle, IForm form, boolean isRewound)
1520        {
1521            expect(cycle.isRewound(form)).andReturn(isRewound);
1522        }
1523    
1524        private void trainRegister(PageRenderSupport support, IForm form, String formId)
1525        {
1526            support.addInitializationScript(form, "dojo.require(\"tapestry.form\");"
1527                                                  + "tapestry.form.registerForm(\"" + formId + "\");");
1528        }
1529    }