🧪 Testes Automatizados
O PlayerWords inclui uma suíte PHPUnit cobrindo lógica de negócio, consultas ao repositório, web services e conformidade com a Privacy API, além de uma suíte Behat cobrindo o jogo, a integração com o PlayerHUD e os relatórios de ponta a ponta num navegador real. Todo push de CI executa a matriz completa (Moodle 4.5 → 5.x, PostgreSQL e MariaDB).
PHPUnit — Testes Centrais
| Arquivo de teste | Casos | O que é coberto |
|---|---|---|
backup_restore_test.php |
6 | Duplicar uma atividade copia suas palavras, renomeia a cópia, reconstrói o cache do curso, e não cria um item de nota duplicado — teste de regressão para a ausência de prepare_activity_structure(); uma referência a item do PlayerHUD sobrevive intacta a um “Duplicar atividade” no mesmo curso (o bloco nunca faz parte desse backup mais estreito); um backup/restauração de curso completo pra um curso novo remapeia a referência pro id novo do item, via o mapeamento playerhud_item que o próprio passo de restauração do block_playerhud registra; uma referência a item de outro curso é descartada em vez de manter apontando pro curso errado — contra o backup_controller/restore_controller real, não um atalho simulado; um backup/restauração de curso completo preserva os modos de pontuação de nota/ranking e tanto score quanto rankingpoints de uma tentativa terminada |
cross_instance_security_test.php |
4 | Estado de sessão, busca de palavra por id, registros de tentativa e a consulta do “registro de tentativas” nunca vazam entre duas instâncias diferentes da atividade, mesmo para o mesmo estudante no mesmo curso |
lib_grant_potential_test.php |
6 | O callback playerhud_grant_potential descoberto pelo teto de “Total XP no jogo” do próprio PlayerHUD: vazio pra uma instância de bloco não reconhecida, pra uma atividade sem item de recompensa configurado, e pra uma atividade ilimitada (reflete a mesma regra antifarm da concessão real); uma atividade limitada retorna uma linha no mesmo formato das entradas de item/missão do próprio PlayerHUD (qtd × rodadas máximas × xp do item); um item de recompensa pertencente à instância de bloco de outro curso não contribui nada; duas atividades limitadas no mesmo curso contribuem cada uma com sua própria linha |
lib_reset_userdata_test.php |
4 | “Redefinir curso” apaga tentativas e reseta notas só quando a opção está marcada, só para o curso alvo, e o padrão do formulário vem marcado |
lib_supports_test.php |
2 | Flags de feature conhecidas retornam o valor documentado e uma desconhecida retorna null; FEATURE_MOD_OTHERPURPOSE (só existe a partir do Moodle 5.1) é checada quando a constante realmente existe na branch que roda o teste |
completion/custom_completion_test.php |
7 | Regra de conclusão customizada (“exigir tentativas”): incompleta abaixo do limite, completa no limite, regra não reportada como disponível quando desabilitada, nomes de regra definidos, descrição inclui a quantidade exigida, ordem de exibição, uma reserva ainda pendente (rodada iniciada mas não terminada) não conta para o limite |
privacy/provider_test.php |
30 | Declaração de metadados (incluindo a preferência de usuário “já viu a introdução”, que vale para o site inteiro) e, para playerwords_attempts, uma correspondência exata do conjunto de campos contra o schema real da tabela; contextos por tentativas; contextos por palavras adicionadas, ambos ignorando uma colisão de instance-id em course_modules com outro tipo de módulo; listar usuários no contexto (no-op para contexto que não é de módulo, e para a mesma colisão); exportar dados do usuário (no-op para lista de contextos vazia, para contexto que não é de módulo, e para um contexto cujo instance-id não resolve); export_user_preferences não relata nada para um usuário que nunca disparou a preferência de introdução vista, e exporta corretamente (componente, nome, descrição) uma vez que disparou; excluir dados do usuário em um único e em múltiplos contextos, no-op para lista de contextos vazia, contexto que não é de módulo, e instance-id não resolvível; excluir dados de todos os usuários num contexto (sem afetar outra atividade; no-op para contexto que não é de módulo e para instance-id não resolvível); excluir dados de uma lista de usuários (no-op para contexto que não é de módulo, instance-id não resolvível, e lista de usuários vazia); export_user_data em múltiplos contextos retorna dados corretamente segregados por contexto, sem misturar atividades; a contagem de leituras no banco fica limitada em vez de escalar linearmente com o número de contextos aprovados, protegendo contra regressão a busca em lote de tentativas/palavras |
lib_update_grades_test.php |
2 | A nota obsoleta de um estudante é realmente limpa (não só deixada intocada) assim que sua última tentativa é removida, então grade_item::has_grades() volta corretamente a false — a correção que permite a exclusão de tentativas no relatório destravar de fato as configurações do mod_form presas a ter nota; um estudante com outras tentativas restantes depois de uma exclusão tem a nota recalculada a partir do que sobrou |
mod_form_test.php |
3 | A trava do Modo de pontuação do ranking reage a dados reais de tentativa, não só ao item de nota: trava assim que existe qualquer tentativa finalizada mesmo numa atividade totalmente sem avaliação, permanece destravada numa atividade recém-criada sem tentativa nenhuma, e não é afetada por uma reserva ainda em aberto (pendente) |
lib_grade_item_update_test.php |
2 | Uma nota de aprovação configurada é realmente aplicada ao item de nota do gradebook, em vez de ser silenciosamente descartada pelo grade_update() do core; passar 'reset' limpa todas as notas registradas contra o item sem excluir o item em si |
| Subtotal | 66 |
Testes de Lógica de Negócio (tests/local/)
| Arquivo de teste | Casos | O que é coberto |
|---|---|---|
ai_word_generator_test.php |
17 | Parsing da resposta de IA (wrapper words/legado concepts, lista nua, cerca de código markdown removida, JSON malformado/não-array, dica cai para definition, entradas não-array ignoradas) e validação de termo como entrada não confiável (palavra única alfabética aceita; termo vazio, multi-palavra e não-alfabético rejeitados; um termo mais longo que a coluna da palavra é rejeitado, e um exatamente no limite ainda é aceito) — tudo via reflection, sem chamada real de IA; o prompt de geração lista as palavras já existentes no banco a evitar quando há alguma, e omite essa seção por completo quando o banco está vazio |
attempts_history_service_test.php |
23 | Registro de tentativas e nota atual do próprio estudante: vazio sem rodadas terminadas; exclui uma reserva ainda pendente; linhas exibidas da mais recente para a mais antiga, enquanto o cálculo da nota usa ordem crescente; a nota computada bate com playerwords_calculate_user_grade() para o método configurado; resumo de nota oculto em atividade não avaliada; texto da palavra cai do conceito para a palavra crua; tempo usado formatado como m:ss; a coluna de pontos no ranking aparece, formatada com 2 casas decimais, quando o ranking está ligado, e é omitida por completo quando está desligado. Relatório de todos os estudantes (get_all_history): tentativas terminadas de todos os estudantes, com o nome de cada um anexado, da mais recente para a mais antiga por padrão; exclui quem pode gerenciar a atividade, tanto das linhas do relatório quanto do dropdown de filtro; o filtro studentid restringe às linhas de um único estudante; a ordenação só aceita colunas da lista permitida, caindo para data em vez de gerar erro numa chave desconhecida; a paginação retorna fatias distintas do conjunto total de resultados; o relatório e o dropdown de filtro de estudante respeitam $CFG->fullnamedisplay e moodle/site:viewfullnames, escondendo o sobrenome de um visualizador sem a capability; delete_attempts() remove a linha e dispara attempt_deleted com o objectid/relateduserid/other corretos, é um no-op para um attemptid de outra instância, é bloqueado pelo SEPARATEGROUPS de excluir um estudante fora do grupo de quem visualiza, e uma chamada em lote sobre dois estudantes retorna os userids afetados deduplicados |
gameplay_service_test.php |
20 | Algoritmo de feedback por letra em 9 combinações de chute/alvo (correto, ausente, presente, letras duplicadas, esgotamento do pool); cálculo de nota para vitória, derrota e notas decimais no modo Binário; modo Linear tanto pro cálculo da nota quanto do ranking (nota cheia nas duas primeiras tentativas, escala proporcionalmente a partir da terceira, fração positiva não-zero na última tentativa permitida, degenera pra nota cheia em toda tentativa quando max_attempts é 2 ou menos, zero quando não completou) |
hud_service_test.php |
28 | Delega pra API \block_playerhud\local\external_items do block_playerhud em toda operação de item, validando pertencimento contra a instância de bloco do próprio chamador em vez de ler as tabelas do PlayerHUD direto: localização do bloco PlayerHUD entre cursos; se o próprio plugin block_playerhud está instalado no site (bate com a checagem real de class_exists); disponibilidade por curso (verdadeiro com instância do bloco, falso sem instância, ignora instância de outro curso); resolução de nome de item (vazio pra item de outra instância de bloco); listagem de itens; consumo de itens (fundos insuficientes, sucesso, ordem FIFO, curto-circuito com quantidade zero, dispensado — não bloqueado — pra item de outra instância de bloco); concessão de itens (linhas de inventário com source='playerwords' mais XP concedido, XP retido quando a chamada sinaliza origem sem limite, item sem XP nunca altera XP em nenhum dos casos, item inexistente, item de instância estranha e quantidade zero são todos no-op); os quatro métodos de custo/concessão de item retornam seus valores neutros documentados em vez de fatalizar quando o block_playerhud não está instalado |
intro_service_test.php |
5 | A preferência de usuário “já viu a introdução”, válida para o site inteiro: falsa por padrão; vira verdadeira e permanece assim depois de marcada (idempotente); isolada por usuário — marcar um usuário nunca afeta outro; o nome da preferência leva o prefixo Frankenstyle do plugin, o contrato do qual tanto o Privacy Provider quanto a limpeza por prefixo do db/uninstall.php dependem |
ranking_service_test.php |
9 | Ranking vazio; ordenação decrescente por pontuação; truncamento top-5 com linha de “outsider” para o usuário atual fora do top; SEPARATEGROUPS filtra para o grupo do próprio estudante; uma reserva ainda pendente (rodada em andamento ou abandonada sem terminar) é excluída do ranking; um usuário que pode gerenciar a atividade (professor editor) nunca aparece no ranking, mesmo com tentativas próprias; o nome exibido respeita $CFG->fullnamedisplay, escondendo o sobrenome de um visualizador sem moodle/site:viewfullnames e mostrando-o por completo pra quem tem a capability |
round_presenter_test.php |
46 | Renderização das linhas da grade; texto de cooldown; mensagens de feedback (desistiu/tempo esgotado/perdeu/venceu, variando pelas tentativas usadas); contexto de ranking; contexto de resultado da rodada (em branco até terminar, revela ao terminar, cooldown reflete mudança posterior de configuração); saldo/custo em item do PlayerHUD no lobby (exibido/oculto pelo estado da rodada, início desabilitado abaixo da quantidade exigida, habilitado quando o saldo cobre), informação de temporizador no lobby; saldo/custo em item do PlayerHUD no botão de dica do painel (exibido/oculto pelo estado de revelação, dica desabilitada abaixo da quantidade exigida, habilitada quando o saldo cobre), o botão de dica fica oculto por completo quando as dicas estão desativadas na atividade, mesmo pra uma palavra que tem dica configurada, temporizador permanece zerado antes da rodada iniciar; resumo de nota até agora (ausente antes de terminar, ausente quando não avaliada, mostra método e nota computada ao terminar, ignora uma tentativa ainda pendente); linha de informação do método de avaliação no lobby (exibida quando relevante, oculta em atividade de rodada única, oculta quando não avaliada); a tecla Ç do teclado só aparece quando o próprio banco de palavras da atividade precisa dela; contador de rodadas jogadas exibido no lobby e no resultado da rodada, usando o símbolo de infinito numa atividade ilimitada e o limite configurado nos demais casos; o rótulo de item concedido pelo PlayerHUD só aparece numa vitória de verdade com item configurado, em branco numa derrota ou quando não configurado; ranking_scoring_explanation() embute a string de detalhe Binário/Linear correspondente ao modo configurado na atividade |
round_service_test.php |
61 | Transições de estado da rodada: palavra sorteada e round_started disparado, e uma rodada finalizada cuja linha de tentativa um professor já excluiu pelo relatório reseta para um estado novo e não finalizado em vez de deixar um resultado obsoleto na tela pra sempre, enquanto o estado de uma rodada ainda em andamento permanece intocado; envio de chute (errado, correto, sem tentativas, após terminar, tamanho incompatível, palavra alvo vazia, caracteres não-alfabéticos, termina quando o tempo esgota no meio do chute mesmo dentro da janela de tolerância do próprio round_expired()); desistência e timeout, cada um rejeitado tanto pra palavra armada mas não iniciada quanto pra nenhuma palavra ativa; nova rodada (reseta o estado, e é um no-op seguro sem sessão prévia); aviso de restrição (limite de rodadas atingido, cooldown ativo, sem restrição); count_rounds_played restrito à instância e ao usuário; cálculo de cooldown (desabilitado, sem tentativas ainda, expirado, reflete mudança posterior de configuração); ensure_round_state retorna imediatamente quando já terminada, recupera sorteando palavra nova após a anterior ser removida no meio da rodada, e retroalimenta a flag roundstarted numa sessão anterior a ela; start_round reserva uma linha de tentativa; finish_round completa a reserva em vez de duplicá-la, insere um registro novo pra uma sessão legada sem reserva a completar, e atualiza o estado de conclusão automática numa vitória de verdade; uma rodada abandonada continua contando para max_rounds; a reserva obsoleta é descartada quando a palavra é removida no meio da rodada; uma rodada vencida concede o item do PlayerHUD configurado com XP quando max_rounds é limitado, concede sem XP quando ilimitado, e uma rodada perdida nunca concede; reveal_hint é rejeitado ao já ter terminado e ao ainda não ter iniciado, e um custo de dica apontando pra um item só desabilitado continua bloqueando quando o saldo está curto; um custo de rodada ou dica apontando pra um item excluído, ou de outro curso, é dispensado em vez de travar o estudante pra sempre; um custo de rodada apontando pra um item só desabilitado (não excluído) continua bloqueando corretamente quando o saldo está curto — desabilitar é reversível, então o custo nunca é dispensado por causa disso |
view_page_service_test.php |
22 | Ramificações de montagem de página: lobby fresco, palavra sorteada persiste entre chamadas, rodada terminada calcula cooldown real, aviso de restrição exibido quando o limite de rodadas é atingido; as URLs de ajuda/registro de tentativas do toolbar sempre estão presentes; a ação de desistir só aparece durante uma rodada ativa; a explicação de critério de desempate do ranking some da ajuda quando o professor desliga o ranking; a explicação do PlayerHUD na ajuda aparece assim que qualquer um entre custo de rodada, custo de dica ou recompensa por vitória está configurado; a ajuda sempre traz a dica apontando de volta pro ícone do toolbar. Introdução automática: verdadeira na primeiríssima carga de página de um usuário em qualquer atividade, e falsa dali em diante — inclusive numa atividade diferente, provando que o escopo é do site inteiro, não por instância — reproduzida igual nas ramificações de lobby fresco, rodada terminada e aviso de restrição. Status do banco de palavras: quem pode gerenciar a atividade vê a contagem de palavras ativas mesmo sem nenhum problema, e, só quando o banco realmente tem alguma, um aviso nomeado de palavra inativa (fora do intervalo de comprimento ou com caractere inválido); um estudante não vê nenhum dos dois; a ramificação de aviso de restrição reporta a rodada como finalizada para que o badge do cronômetro no cabeçalho fique oculto em vez de mostrar um “0” perdido |
word_normalizer_test.php |
16 | Normalização sem acentuação em 8 combinações de diacríticos; is_valid_charset aceita só letras (inclusive acentuadas) e rejeita dígito, espaço, hífen, apóstrofo e string vazia, em 8 casos |
words_repository_test.php |
65 | Seleção de palavra (pool vazio, exclusão de não aprovados/muito curtos/muito longos/caracteres não-letra, modo aleatório, determinismo e ciclagem da sequência compartilhada, evita a palavra excluída quando há alternativa, permite-a de volta quando é a única candidata); get_last_played_word_id (0 sem rodadas terminadas, ignora uma reserva pendente); inserção de palavra manual e por IA, esta última pulando uma palavra já presente no banco; checagem de duplicata word_exists (correspondência case-insensitive, sem correspondência, restrita à instância, corresponde independente da fonte, ignora o próprio id ao renomear); busca, atualização e exclusão de palavra restritas à instância dona; exclusão e aprovação em lote; listagem de palavras recentes, paginada, com join do nome do glossário; sincronização de glossário (divisão de conceito multi-palavra, filtro de stopwords configuráveis, atualização de dica em re-sincronização sem duplicar, limpeza de órfãos quando uma entrada desaparece, modo glossaryid = 0 cobrindo todos os glossários do curso, pula um conceito cujo texto já pertence a uma palavra manual/IA sem alterar a dica dela, pula um token mais longo que a coluna da palavra em vez de abortar toda a sincronização, e — com glossary_split_concepts desligado — um conceito de várias palavras pulado por completo em vez de dividido, com um conceito irmão de uma palavra só não sendo afetado, e que esse é o padrão da instância quando o ajuste nunca é definido explicitamente); a tecla Ç do teclado virtual só é oferecida quando uma palavra aprovada realmente contém uma, restrito à própria atividade e ignorando palavras não aprovadas. get_fragmented_concepts relata um conceito de glossário dividido em várias palavras irmãs, exclui um conceito de palavra única, ignora palavras manuais/IA mesmo quando uma coincidentemente compartilha o texto com o próprio conceito, e é restrito à própria instância. get_inactive_words fica vazio quando não há nada errado, relata uma palavra fora do intervalo de comprimento atual e uma palavra com caractere inválido (cada uma marcada com o motivo), e ignora palavras não aprovadas. get_draw_counts fica ausente — não zero — para uma palavra nunca sorteada, soma toda tentativa independente do resultado, e é restrito à própria instância. count_glossary_candidates (a mesma prévia usada antes mesmo de a atividade existir) conta só candidatas dentro do intervalo pedido pra um glossário; glossaryid = 0 cobre todos os glossários do curso; dois conceitos que tokenizam pra mesma palavra só contam uma vez; um id de glossário de outro curso nunca é contado, mesmo sendo um id real; um curso sem nenhum glossário conta zero sem erro; zero para um conceito de várias palavras quando splitconcepts é passado como falso; get_recent_words cai pro padrão seguro de ordenação (id/DESC) quando recebe coluna ou direção fora da lista permitida, em vez de confiar no chamador |
| Subtotal | 312 |
Testes de Web Services (tests/external/)
| Arquivo de teste | Casos | O que é coberto |
|---|---|---|
count_eligible_words_test.php |
6 | Conta só palavras aprovadas do banco cujo comprimento cai dentro do intervalo pedido; exclui palavras não aprovadas e fora do intervalo; restrito à própria instância da atividade; exige a capability mod/playerwords:addinstance (rejeita um estudante) |
count_glossary_candidates_test.php |
5 | Conta palavras candidatas de um glossário específico dentro do intervalo de comprimento pedido; uma palavra fora do intervalo é excluída; uma stopword passada diretamente do formulário de configurações (ainda sem instância salva) remove o token correspondente antes da contagem; o parâmetro splitconcepts, vindo direto do estado da própria caixa de seleção no formulário de configurações, zera a contribuição de um conceito de várias palavras quando falso; exige a capability mod/playerwords:addinstance (rejeita um estudante) |
end_round_test.php |
6 | Desistência termina a rodada; timeout termina a rodada; um valor inválido de reason é rejeitado; a capability mod/playerwords:view é exigida |
new_round_test.php |
5 | Nova rodada sorteia palavra nova; bloqueado quando o limite de rodadas já foi atingido; a capability mod/playerwords:view é exigida; bloqueado enquanto uma rodada está genuinamente em andamento, fechando a brecha onde um cliente podia descartar uma rodada perdedora antes de ser registrada; bloqueado enquanto uma palavra está apenas armada no lobby (ainda não iniciada), fechando uma brecha de re-sorteio grátis por comprimento/dificuldade da palavra |
reveal_hint_test.php |
8 | Dica é revelada; revelar duas vezes é idempotente; rejeitado após a rodada terminar; rejeitado para a atividade inteira quando as dicas estão desativadas, mesmo com a palavra sorteada tendo uma dica configurada; a capability mod/playerwords:view é exigida; saldo insuficiente de item do PlayerHUD (item real, válido) bloqueia a revelação; um custo apontando pra um item excluído é dispensado em vez disso |
start_round_test.php |
7 | Cronômetro da rodada inicia; rejeitado quando já iniciado; a capability mod/playerwords:view é exigida; saldo insuficiente de item do PlayerHUD (item real, válido) bloqueia o início; um custo apontando pra um item excluído é dispensado em vez disso |
submit_guess_test.php |
10 | Um chute errado nunca revela a palavra; um chute correto revela só quando termina; um chute perdedor também revela; a capability mod/playerwords:view é exigida; timeleft reflete os segundos restantes durante a rodada; timeleft fica congelado no momento em que a rodada terminou, não no relógio real; um total de ranking fracionado sobrevive à limpeza de retorno da API externa, contra a chamada real da webservice |
| Subtotal | 47 |
| Total Geral | 425 |
vendor/bin/phpunit --testsuite mod_playerwords
Cobertura de linhas por classe (PHPUnit + Xdebug):
| Classe | Cobertura de linhas |
|---|---|
completion\custom_completion |
100% |
event\attempt_deleted |
46% |
external\count_eligible_words |
100% |
external\count_glossary_candidates |
100% |
external\end_round |
100% |
external\new_round |
100% |
external\reveal_hint |
98% |
external\start_round |
100% |
external\submit_guess |
100% |
local\ai_word_generator |
36% |
local\attempts_history_service |
98% |
local\gameplay_service |
95% |
local\hud_service |
91% |
local\intro_service |
100% |
local\ranking_service |
98% |
local\round_presenter |
98% |
local\round_service |
99% |
local\view_page_service |
97% |
local\word_normalizer |
100% |
local\words_repository |
99% |
privacy\provider |
100% |
| Geral | 90% |
A maioria das outras classes event/*.php não aparece na lista — o Moodle só as carrega sob
demanda quando o evento correspondente realmente dispara, então a instrumentação nunca chega a
vê-las. attempt_deleted é a exceção, já que attempts_history_service_test.php genuinamente a
constrói e dispara: seus 46% cobrem init() e validate_data() (exercitados por toda chamada
de create()/trigger()), enquanto get_name(), get_description() e
get_objectid_mapping() ficam sem cobertura — eles só rodam ao renderizar de fato uma entrada
de log ou mapear uma restauração, nenhum dos quais uma simples asserção de “o evento carrega o
objectid/relateduserid certos” exercita.
hud_service carrega um gap deliberado: get_available_quantity(), get_item_name(),
consume_items() e grant_items() carregam cada uma uma checagem
if (!self::is_installed()) que retorna um valor neutro — o caminho de fallback para um site
onde o block_playerhud nunca foi instalado ou já foi removido. Todo ambiente de dev e CI deste
plugin instala o block_playerhud junto, então esses quatro ramos só são exercitados por
test_item_methods_return_neutral_values_when_not_installed() num site que genuinamente não tem
o bloco; aqui o teste fica skipped. Os próprios métodos de ai_word_generator que chamam IA de
verdade (call_ai(), call_core_ai(), generate_and_save()…) são o outro gap deliberado:
cobri-los exigiria mockar as chamadas HTTP reais do local_aihub/core_ai, julgado não valer a
pena por ora.
Behat — Testes de Ponta a Ponta
O PlayerWords também inclui uma suíte Behat que conduz o jogo numa sessão de navegador real, cobrindo o jogo em si, a integração com o PlayerHUD, os relatórios voltados ao professor e a barra de ferramentas/modais — áreas que um teste unitário PHPUnit não consegue exercitar (interface dirigida por JavaScript, navegação real de página).
| Arquivo de feature | Cenários | O que é coberto |
|---|---|---|
mod_playerwords_smoke.feature |
1 | O lobby carrega e uma rodada pode ser iniciada — a verificação básica sobre a qual o resto da suíte se apoia |
mod_playerwords_gameplay.feature |
7 | Vencer uma rodada na primeira tentativa esconde o selo do cronômetro; perder uma rodada ao esgotar todas as tentativas; as setas do teclado movem o foco entre as caixas de uma linha de chute sem alterar nenhum valor; desistir de uma rodada ativa pede confirmação; uma rodada termina automaticamente quando seu cronômetro se esgota; atingir o limite de rodadas oculta a ação de nova rodada em vez de um beco sem saída; um intervalo configurado mostra uma contagem regressiva em vez do botão de nova rodada |
mod_playerwords_playerhud.feature |
4 | O lobby bloqueia o início de uma rodada até o estudante poder pagar o custo do item configurado; revelar a dica pede confirmação e saldo suficiente; uma rodada inicia e a dica é revelada de graça quando o item configurado não existe mais; vencer uma rodada concede o item configurado do PlayerHUD |
mod_playerwords_reports.feature |
5 | Um estudante vê só o próprio histórico de tentativas, nunca o de outro estudante; o relatório de todos os estudantes do professor pagina além de 30 linhas, ordena ao clicar num cabeçalho de coluna e filtra para um único estudante; a página de ranking mostra o top 5 mais a linha do usuário atual quando ele fica fora dele |
mod_playerwords_settings.feature |
4 | O modo de pontuação e o número de tentativas travam assim que existe uma nota real; adicionar uma palavra manual já existente no banco, ou com um caractere que o jogo não consegue usar, é rejeitado; um item do PlayerHUD que não existe mais permanece selecionado no formulário de configurações em vez de resetar silenciosamente |
mod_playerwords_toolbar.feature |
9 | O ícone de gerenciar palavras e o aviso de palavras inativas só aparecem para quem gerencia a atividade; o ícone de ranking só aparece quando o ranking está ativado; o ícone de desistir só aparece durante uma rodada ativa; o botão de dica só aparece quando as dicas estão ativadas na atividade; o modal de ajuda mostra seus parágrafos opcionais só quando relevantes, e os oculta caso contrário; o modal “Como jogar” abre automaticamente na primeiríssima visita de um jogador, uma única vez; cancelar a confirmação de desistência deixa a rodada intocada |
| Subtotal | 30 |
vendor/bin/behat --config public/behat.yml --profile=chrome --tags @mod_playerwords